前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >React源码解析之Commit最后子阶段「layout」(附Commit阶段流程图)

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

作者头像
进击的小进进
发布2020-04-30 18:10:44
9260
发布2020-04-30 18:10:44
举报

前言

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

代码语言:javascript
复制
    //=============第三个 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 的引用

源码:

代码语言:javascript
复制
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」后,再去清除副作用:

代码语言:javascript
复制
  //判断本次 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() 获取焦点

源码:

代码语言:javascript
复制
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()都会执行:

代码语言:javascript
复制
  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()源码:

代码语言:javascript
复制
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()源码:

代码语言:javascript
复制
//可以 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源码:

代码语言:javascript
复制
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()源码:

代码语言:javascript
复制
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)callbackcapturedEffect链上的callbackcomponentDidCatch()

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

四、commitAttachRef()

作用: 获取 instance 实例,并指定给 ref

源码:

代码语言:javascript
复制
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阶段流程图

GitHub

commitLayoutEffects()https://github.com/AttackXiaoJinJin/reactExplain/blob/master/react16.8.6/packages/react-reconciler/src/ReactFiberWorkLoop.js

commitLayoutEffectOnFiber() as commitLifeCycles()/commitAttachRef()https://github.com/AttackXiaoJinJin/reactExplain/blob/master/react16.8.6/packages/react-reconciler/src/ReactFiberCommitWork.js

commitUpdateQueue() https://github.com/AttackXiaoJinJin/reactExplain/blob/master/react16.8.6/packages/react-reconciler/src/ReactUpdateQueue.js

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-04-27,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 webchen 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 一、commitLayoutEffects()
  • 二、commitLayoutEffectOnFiber()
  • 三、commitUpdateQueue()
  • 四、commitAttachRef()
  • 总结
  • Commit阶段流程图
  • GitHub
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档