Render阶段

# Render阶段

在本节正式开始前,让我们复习下这一章到目前为止所学的。

Renderer工作的阶段被称为commit阶段。commit阶段可以分为三个子阶段:

  • before mutation阶段(执行DOM操作前)
  • mutation阶段(执行DOM操作)
  • layout阶段(执行DOM操作后)

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

# 概览

before mutation阶段的代码很短,整个过程就是遍历effectList并调用commitBeforeMutationEffects函数处理。

这部分源码在这里 (opens new window) (opens new window)。为了增加可读性,示例代码中删除了不相关的逻辑

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

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

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

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

focusedInstanceHandle = null;

我们重点关注beforeMutation阶段的主函数commitBeforeMutationEffects做了什么。

# commitBeforeMutationEffects

大体代码逻辑:

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;
  }
}

整体可以分为三部分:

  1. 处理DOM节点渲染/删除后的 autoFocusblur 逻辑。
  2. 调用getSnapshotBeforeUpdate生命周期钩子。
  3. 调度useEffect

我们讲解下2、3两点。

# 调用getSnapshotBeforeUpdate

commitBeforeMutationEffectOnFibercommitBeforeMutationLifeCycles的别名。

在该方法内会调用getSnapshotBeforeUpdate

你可以在这里 (opens new window) (opens new window)看到这段逻辑

Reactv16开始,componentWillXXX钩子前增加了UNSAFE_前缀。

究其原因,是因为Stack Reconciler重构为Fiber Reconciler后,render阶段的任务可能中断/重新开始,对应的组件在render阶段的生命周期钩子(即componentWillXXX)可能触发多次。

这种行为和Reactv15不一致,所以标记为UNSAFE_

更详细的解释参照这里(opens new window) (opens new window)

为此,React提供了替代的生命周期钩子getSnapshotBeforeUpdate

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