第三章render函数-提交阶段:3.5 before mutation 阶段

125 阅读2分钟

本节我们看看before mutation阶段(执行DOM操作前)都做了什么。

整个过程就是遍历effectList并调用commitBeforeMutationEffects函数处理。我们重点关注beforeMutation阶段的主函数commitBeforeMutationEffects做了什么。

// 保存之前的优先级,以同步优先级执行,执行完毕后恢复之前优先级
const previousLanePriority = getCurrentUpdateLanePriority();
setCurrentUpdateLanePriority(SyncLanePriority);

// 将当前上下文标记为CommitContext,作为commit阶段的标志
const prevExecutionContext = executionContext;
executionContext |= CommitContext;

// 处理focus状态
focusedInstanceHandle = prepareForCommit(root.containerInfo);
shouldFireAfterActiveInstanceBlur = false;

// beforeMutation阶段的主函数
commitBeforeMutationEffects(finishedWork);

focusedInstanceHandle = null;

commitBeforeMutationEffects

整体可以分为三部分:

  • 处理DOM节点渲染/删除后的 autoFocus、blur 逻辑。
  • 调用getSnapshotBeforeUpdate生命周期钩子
  • 调度useEffect.
function commitBeforeMutationEffects() {
  while (nextEffect !== null) {
    const current = nextEffect.alternate;

    if (!shouldFireAfterActiveInstanceBlur && focusedInstanceHandle !== null) {
      // ...focus blur相关
    }

    const effectTag = nextEffect.effectTag;

    // 调用getSnapshotBeforeUpdate
    if ((effectTag & Snapshot) !== NoEffect) {
      commitBeforeMutationEffectOnFiber(current, nextEffect);
    }

    // 调度useEffect
    if ((effectTag & Passive) !== NoEffect) {
      if (!rootDoesHavePassiveEffects) {
        rootDoesHavePassiveEffects = true;
        scheduleCallback(NormalSchedulerPriority, () => {
          flushPassiveEffects();
          return null;
        });
      }
    }
    nextEffect = nextEffect.nextEffect;
  }
}

调用getSnapshotBeforeUpdate

从Reactv16开始,componentWillXXX钩子前增加了UNSAFE前缀,究其原因,是因为Stack Reconciler重构为Fiber Reconciler后,render阶段的任务可能中断/重新开始,对应的组件在render阶段的生命周期钩子(即componentWillXXX)可能触发多次。这种行为和Reactv15不一致,所以标记为UNSAFE。为此,React提供了替代的生命周期钩子getSnapshotBeforeUpdate。

getSnapshotBeforeUpdate是在commit阶段内的before mutation阶段调用的,由于commit阶段是同步的,所以不会遇到多次调用的问题。

调度useEffect

在这几行代码内,scheduleCallback方法由Scheduler模块提供,用于以某个优先级异步调度一个回调函数。在此处,被异步调度的回调函数就是触发useEffect的方法flushPassiveEffects。

在flushPassiveEffects方法内部会从全局变量rootWithPendingPassiveEffects获取effectList。effectList中保存了需要执行副作用的Fiber节点。其中副作用包括

  • 插入DOM节点(Placement)
  • 更新DOM节点(Update)
  • 删除DOM节点(Deletion)

除此外,当一个FunctionComponent含有useEffect或useLayoutEffect,他对应的Fiber节点也会被赋值effectTag。在flushPassiveEffects方法内部会遍历rootWithPendingPassiveEffects(即effectList)执行effect回调函数。

所以整个useEffect异步调用分为三步:

  1. before mutation阶段在scheduleCallback中调度flushPassiveEffects
  2. layout阶段之后将effectList赋值给rootWithPendingPassiveEffects
  3. scheduleCallback触发flushPassiveEffects,flushPassiveEffects内部遍历rootWithPendingPassiveEffects

useEffect异步执行的原因主要是防止同步执行时阻塞浏览器渲染。

// 调度useEffect
if ((effectTag & Passive) !== NoEffect) {
  if (!rootDoesHavePassiveEffects) {
    rootDoesHavePassiveEffects = true;
    scheduleCallback(NormalSchedulerPriority, () => {
      // 触发useEffect
      flushPassiveEffects();
      return null;
    });
  }
}