Javascript

React源码解析之Commit最后子阶段「layout」(附Commit阶段流程图)

本文主要是介绍React源码解析之Commit最后子阶段「layout」(附Commit阶段流程图),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

前言
今天我们来看下Commit最后子阶段layout的源码:

    //=============第三个 while 循环==========================
    do {
      if (__DEV__) {
        invokeGuardedCallback(
          null,
          commitLayoutEffects,
          null,
          root,
          expirationTime,
        );
        //删除了 dev 代码
      } else {
        try {
          //commit lifecycles,也就是触发生命周期的 api

          //① 循环 effect 链,针对不同的 fiber 类型,进行effect.destroy()/componentDidMount()/callback/node.focus()等操作
          //② 指定 ref 的引用
          commitLayoutEffects(root, expirationTime);
        } catch (error) {
          invariant(nextEffect !== null, 'Should be working on an effect.');
          captureCommitPhaseError(nextEffect, error);
          nextEffect = nextEffect.nextEffect;
        }
      }
    } while (nextEffect !== null);
复制代码

一、commitLayoutEffects()
作用:
① 循环effect链,针对不同的fiber类型,进行effect.destroy()/create()/componentDidMount()/callback/node.focus()等操作
② 指定 ref 的引用

源码:

function commitLayoutEffects(
  root: FiberRoot,
  committedExpirationTime: ExpirationTime,
) {
  // TODO: Should probably move the bulk of this function to commitWork.
  //循环 effect 链
  while (nextEffect !== null) {
    //dev 环境代码,不看
    setCurrentDebugFiberInDEV(nextEffect);

    const effectTag = nextEffect.effectTag;
    //如果有 Update、Callback 的 effectTag 的话
    if (effectTag & (Update | Callback)) {
      recordEffect();
      const current = nextEffect.alternate;
      //重点看 FunctionComponent/ClassComponent/HostComponent
      //① FunctionComponent——执行effect.destroy()/effect.create()
      //② ClassComponent——componentDidMount()/componentDidUpdate(),effect 链——执行 setState 的 callback,capturedEffect 链执行 componentDidCatch
      //③ HostComponent——判断是否是自动聚焦的 DOM 标签,是的话则调用 node.focus() 获取焦点
      commitLayoutEffectOnFiber(
        root,
        current,
        nextEffect,
        committedExpirationTime,
      );
    }
    //指定 ref 的引用
    if (effectTag & Ref) {
      recordEffect();
      //获取 instance 实例,并指定给 ref
      commitAttachRef(nextEffect);
    }
    //副作用
    if (effectTag & Passive) {
      rootDoesHavePassiveEffects = true;
    }
    //dev 环境,不看
    resetCurrentDebugFiberInDEV();
    nextEffect = nextEffect.nextEffect;
  }
}
复制代码

解析:
循环 effect 链,执行:
(1) 当有Update/CallbackeffectTag的话,执行commitLayoutEffectOnFiber(),对不同的fiber,进行effect.destroy()/componentDidMount()/callback/node.focus()等操作

(2) 当有RefeffectTag的话,执行commitAttachRef(),获取fiberinstance实例,并指定给ref

(3) 当有PassiveeffectTag的话,表示有副作用,将rootDoesHavePassiveEffects标记为true,方便在子阶段「layout」后,再去清除副作用:

  //判断本次 commit 是否会产生新的更新,也就是脏作用
  const rootDidHavePassiveEffects = rootDoesHavePassiveEffects;
  //如果有脏作用的处理
  if (rootDoesHavePassiveEffects) {
    // This commit has passive effects. Stash a reference to them. But don't
    // schedule a callback until after flushing layout work.
    rootDoesHavePassiveEffects = false;
    rootWithPendingPassiveEffects = root;
    pendingPassiveEffectsExpirationTime = expirationTime;
  }
复制代码

具体的流程,请看:
React源码解析之commitRoot整体流程概览

接下来,我们看下commitLayoutEffectOnFiber()commitAttachRef()

二、commitLayoutEffectOnFiber()
作用:
重点看FunctionComponent/ClassComponent/HostComponent
① FunctionComponent——执行effect.destroy()/effect.create()
② ClassComponent——componentDidMount()/componentDidUpdate()effect链——执行setStatecallbackcapturedEffect链执行componentDidCatch()
③ HostComponent——判断是否是自动聚焦的 DOM 标签,是的话则调用 node.focus() 获取焦点

源码:

function commitLifeCycles(
  finishedRoot: FiberRoot,
  current: Fiber | null,
  finishedWork: Fiber,
  committedExpirationTime: ExpirationTime,
): void {
  switch (finishedWork.tag) {
    case FunctionComponent:
    case ForwardRef:
    case SimpleMemoComponent: {
      //循环 FunctionComponent 上的 effect 链,执行 effect.destroy()/create(),类似于 componentWillUnmount()/componentDidMount()
      commitHookEffectList(UnmountLayout, MountLayout, finishedWork);
      break;
    }
    case ClassComponent: {
      const instance = finishedWork.stateNode;
      //有 update 的 effectTag 的话
      if (finishedWork.effectTag & Update) {
        //如果是第一次渲染的话,则执行 componentDidMount()
        if (current === null) {
          startPhaseTimer(finishedWork, 'componentDidMount');
          // We could update instance props and state here,
          // but instead we rely on them being set during last render.
          // TODO: revisit this when we implement resuming.
          if (__DEV__) {
            //删除了 dev 代码
          }
          instance.componentDidMount();
          stopPhaseTimer();
        }
        //如果是多次渲染的话,则执行 componentDidUpdate()
        else {
          const prevProps =
            finishedWork.elementType === finishedWork.type
              ? current.memoizedProps
              : resolveDefaultProps(finishedWork.type, current.memoizedProps);
          const prevState = current.memoizedState;
          startPhaseTimer(finishedWork, 'componentDidUpdate');
          // We could update instance props and state here,
          // but instead we rely on them being set during last render.
          // TODO: revisit this when we implement resuming.
          //删除了 dev 代码
          if (__DEV__) {

          }
          instance.componentDidUpdate(
            prevProps,
            prevState,
            instance.__reactInternalSnapshotBeforeUpdate,
          );
          stopPhaseTimer();
        }
      }
      const updateQueue = finishedWork.updateQueue;
      //如果更新队列不为空的话
      if (updateQueue !== null) {
        //删除了 dev 代码
        if (__DEV__) {

        }
        // We could update instance props and state here,
        // but instead we rely on them being set during last render.
        // TODO: revisit this when we implement resuming.
        //将 capturedUpdate 队列放到 update 队列末尾
        //循环 effect 链,执行 setState 的 callback
        //清除 effect 链
        //循环 capturedEffect 链,执行 componentDidCatch
        //清除 capturedEffect 链
        commitUpdateQueue(
          finishedWork,
          updateQueue,
          instance,
          committedExpirationTime,
        );
      }
      return;
    }
    //fiberRoot 节点,暂时跳过
    case HostRoot: {
      const updateQueue = finishedWork.updateQueue;
      if (updateQueue !== null) {
        let instance = null;
        if (finishedWork.child !== null) {
          switch (finishedWork.child.tag) {
            case HostComponent:
              instance = getPublicInstance(finishedWork.child.stateNode);
              break;
            case ClassComponent:
              instance = finishedWork.child.stateNode;
              break;
          }
        }
        commitUpdateQueue(
          finishedWork,
          updateQueue,
          instance,
          committedExpirationTime,
        );
      }
      return;
    }
    //DOM 标签
    case HostComponent: {
      const instance: Instance = finishedWork.stateNode;

      // Renderers may schedule work to be done after host components are mounted
      // (eg DOM renderer may schedule auto-focus for inputs and form controls).
      // These effects should only be committed when components are first mounted,
      // aka when there is no current/alternate.
      //如果是第一次渲染,并且该节点需要更新的 haul,就需要判断是否是自动聚焦的 DOM 标签
      if (current === null && finishedWork.effectTag & Update) {
        const type = finishedWork.type;
        const props = finishedWork.memoizedProps;
        // 判断是否是自动聚焦的 DOM 标签
        commitMount(instance, type, props, finishedWork);
      }

      return;
    }
    //文本节点,无生命周期方法
    case HostText: {
      // We have no life-cycles associated with text.
      return;
    }
    case HostPortal: {
      // We have no life-cycles associated with portals.
      return;
    }
    //以下的情况也跳过
    case Profiler: {
      if (enableProfilerTimer) {
        const onRender = finishedWork.memoizedProps.onRender;

        if (enableSchedulerTracing) {
          onRender(
            finishedWork.memoizedProps.id,
            current === null ? 'mount' : 'update',
            finishedWork.actualDuration,
            finishedWork.treeBaseDuration,
            finishedWork.actualStartTime,
            getCommitTime(),
            finishedRoot.memoizedInteractions,
          );
        } else {
          onRender(
            finishedWork.memoizedProps.id,
            current === null ? 'mount' : 'update',
            finishedWork.actualDuration,
            finishedWork.treeBaseDuration,
            finishedWork.actualStartTime,
            getCommitTime(),
          );
        }
      }
      return;
    }
    case SuspenseComponent:
    case SuspenseListComponent:
    case IncompleteClassComponent:
      return;
    case EventComponent: {
      if (enableFlareAPI) {
        mountEventComponent(finishedWork.stateNode);
      }
      return;
    }
    default: {
      invariant(
        false,
        'This unit of work tag should not have side-effects. This error is ' +
          'likely caused by a bug in React. Please file an issue.',
      );
    }
  }
}
复制代码

解析:
我们重点看下FunctionComponent/ClassComponent/HostComponent这三种情况:
(1) 如果是FunctionComponent的话,则执行commitHookEffectList(),循环effect链,执行 effect.destroy()/effect.create(),类似于componentWillUnmount()/componentDidMount()

关于commitHookEffectList()的讲解,请看:
React源码解析之Commit第一子阶段「before mutation」 中的三、commitHookEffectList()

需要注意下传的参数——commitHookEffectList(UnmountLayout, MountLayout, finishedWork)

因为是UnmountLayoutMountLayout,所以effect.destroy()/effect.create()都会执行:

  if ((effect.tag & unmountTag) !== NoHookEffect) {
        // Unmount
        const destroy = effect.destroy;
        effect.destroy = undefined;
        if (destroy !== undefined) {
          destroy();
        }
      }
      //如果包含 mountTag 这个 effectTag 的话,执行 create()
      if ((effect.tag & mountTag) !== NoHookEffect) {
        // Mount
        const create = effect.create;
        effect.destroy = create();

        if (__DEV__) {
          //删除了 dev 代码
        }
      }
复制代码

(2) 如果是ClassComponent的话,先分两种情况:
① 如果是第一次渲染的话,则执行componentDidMount()
② 多次渲染的话,则执行componentDidUpdate()

然后是循环更新队列updateQueue
③ 当更新队列updateQueue不为空时,执行commitUpdateQueue(),循环effect链,执行 setState()callback,循环capturedEffect链,执行componentDidCatch()

commitUpdateQueue()方法留在后面讲

(2) 如果是DOM标签HostComponent的话,当第一次渲染时,执行commitMount(),判断是否是自动聚焦的DOM标签,是的话则调用node.focus()获取焦点

commitMount()源码:

export function commitMount(
  domElement: Instance,
  type: string,
  newProps: Props,
  internalInstanceHandle: Object,
): void {
  // Despite the naming that might imply otherwise, this method only
  // fires if there is an `Update` effect scheduled during mounting.
  // This happens if `finalizeInitialChildren` returns `true` (which it
  // does to implement the `autoFocus` attribute on the client). But
  // there are also other cases when this might happen (such as patching
  // up text content during hydration mismatch). So we'll check this again.
  //判断是否是自动聚焦的 DOM 标签,是的话则调用 node.focus() 获取焦点
  if (shouldAutoFocusHostComponent(type, newProps)) {
    ((domElement: any):
      | HTMLButtonElement
      | HTMLInputElement
      | HTMLSelectElement
      | HTMLTextAreaElement).focus();
  }
}
复制代码

shouldAutoFocusHostComponent()源码:

//可以 foucus 的节点返回autoFocus的值,否则返回 false
function shouldAutoFocusHostComponent(type: string, props: Props): boolean {
  //可以 foucus 的节点返回autoFocus的值,否则返回 false
  switch (type) {
    case 'button':
    case 'input':
    case 'select':
    case 'textarea':
      return !!props.autoFocus;
  }
  return false;
}
复制代码

三、commitUpdateQueue()
作用:
① 将capturedUpdate队列放到update队列末尾
② 循环effect链,执行effect上的callback,也就是this.setState({},()=>{})里的callback
③ 清除effect
④ 循环capturedEffect链,执行componentDidCatch()
⑤ 清除capturedEffect
源码:

export function commitUpdateQueue<State>(
  finishedWork: Fiber,
  finishedQueue: UpdateQueue<State>,
  instance: any,
  renderExpirationTime: ExpirationTime,
): void {
  // If the finished render included captured updates, and there are still
  // lower priority updates left over, we need to keep the captured updates
  // in the queue so that they are rebased and not dropped once we process the
  // queue again at the lower priority.
  //如果目标节点 render 时,捕获到了 update error,并且仍有低优先级的 update 未执行,那么 React 会在
  //队列中保持这 update error,并去让低优先级的 update 去执行该 update error

  //在更新时,捕获到了 error
  //如果 update 队列仍存在,则将 capturedUpdate 放到正常 update 队列的末尾
  //清除 capturedUpdate 链表
  if (finishedQueue.firstCapturedUpdate !== null) {
    // Join the captured update list to the end of the normal list.
    //将 capturedUpdate 链表放到正常 update 队列的末尾
    if (finishedQueue.lastUpdate !== null) {
      finishedQueue.lastUpdate.next = finishedQueue.firstCapturedUpdate;
      finishedQueue.lastUpdate = finishedQueue.lastCapturedUpdate;
    }
    // Clear the list of captured updates.
    //将 capturedUpdate 放到正常 update 队列的末尾后,清除 capturedUpdate 链表
    finishedQueue.firstCapturedUpdate = finishedQueue.lastCapturedUpdate = null;
  }

  // Commit the effects
  //循环 effect 链,执行 effect 上的 callback,也就是 this.setState({},()=>{})里的 callback
  commitUpdateEffects(finishedQueue.firstEffect, instance);
  //清除 effect 链
  finishedQueue.firstEffect = finishedQueue.lastEffect = null;
  //循环 capturedEffect 链,执行 capturedEffect 上的 callback,即 componentDidCatch()
  commitUpdateEffects(finishedQueue.firstCapturedEffect, instance);
  //清除 capturedEffect 链
  finishedQueue.firstCapturedEffect = finishedQueue.lastCapturedEffect = null;
}
复制代码

解析:
代码结构和作用上的顺序一致,就不赘述了,看下commitUpdateEffects()源码:

function commitUpdateEffects<State>(
  effect: Update<State> | null,
  instance: any,
): void {
  while (effect !== null) {
    const callback = effect.callback;
    if (callback !== null) {
      effect.callback = null;
      //源码:callback.call(context);
      //注意是用 .call() 来执行 callback 的,目的就是指定 this
      callCallback(callback, instance);
    }
    effect = effect.nextEffect;
  }
}
复制代码

循环effect链,执行effect上的callback
注意:
effect链上的callbackthis.setState({},callback)callback
capturedEffect链上的callbackcomponentDidCatch()

最后讲下快被遗忘的commitAttachRef()

四、commitAttachRef()
作用:
获取 instance 实例,并指定给 ref

源码:

function commitAttachRef(finishedWork: Fiber) {
  const ref = finishedWork.ref;
  if (ref !== null) {
    const instance = finishedWork.stateNode;
    let instanceToUse;
    //获取可使用的 instance(实例)
    switch (finishedWork.tag) {
      //DOM标签
      case HostComponent:
        instanceToUse = getPublicInstance(instance);
        break;
      default:
        instanceToUse = instance;
    }
    //指定 ref 的引用
    if (typeof ref === 'function') {
      ref(instanceToUse);
    } else {
      //删除了 dev 代码
      ref.current = instanceToUse;
    }
  }
}
复制代码

解析:
(1) 如果 ref 是 function,则执行ref(instance)
(2) 如果 ref 是 object,则执行ref.current = instance

总结
子阶段「layout」的两部分逻辑
(1) 循环effect链,针对不同的fiber类型,进行不同的操作
① FunctionComponent——执行effect.destroy()/effect.create()
② ClassComponent——componentDidMount()/componentDidUpdate()effect链——执行setStatecallbackcapturedEffect链执行componentDidCatch()
③ HostComponent——首次渲染判断是否是自动聚焦的 DOM 标签,是的话则调用node.focus()获取焦点

(2) 指定ref的引用
ref(instance)
ref.current = instance

Commit阶段流程图

Commit阶段流程图.jpg
Commit阶段流程图.jpg

GitHub
commitLayoutEffects()
github.com/AttackXiaoJ…

commitLayoutEffectOnFiber() as commitLifeCycles()/commitAttachRef()
github.com/AttackXiaoJ…

commitUpdateQueue()
github.com/AttackXiaoJ…


(完)

这篇关于React源码解析之Commit最后子阶段「layout」(附Commit阶段流程图)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!