React18 commit阶段概览

182 阅读2分钟

commit阶段流程

主要分为三个阶段:

  • before mutation
  • mutation
  • layout

finishedWork节点是render阶段结束挂载在wipHostRootFiber上的一个属性,是已经打上flags后的新的根fiber。

在commit阶段首先会判断是否需要进入三阶段:

 const subtreeHasEffects =
    (finishedWork.subtreeFlags &
      (BeforeMutationMask | MutationMask | LayoutMask | PassiveMask)) !==
    NoFlags;
  const rootHasEffect =
    (finishedWork.flags &
      (BeforeMutationMask | MutationMask | LayoutMask | PassiveMask)) !==
    NoFlags;

if (subtreeHasEffects || rootHasEffect) {
  
} else {
    // No effects.
    root.current = finishedWork;
    // Measure these anyway so the flamegraph explicitly shows that there were
    // no effects.
  
  }

没有相应的flags则会跳过commit阶段,其中三阶段相关的flags常量为:

export const BeforeMutationMask = Update | Snapshot;
export const MutationMask = Placement | Update | ChildDeletion | ContentReset | Ref | Hydrating | Visibility;
export const LayoutMask = Update | Callback | Ref | Visibility;
export const PassiveMask = Passive | ChildDeletion;

子阶段的执行流程大致分为commitXXXEffects_begincommitXXXEffects_complete

function commitXXXEffects(
  root: FiberRoot,
  firstChild: Fiber,
) {
  // 省略全局变量初始化等操作
  // firstChild 为 wipHostRootFiber节点上的 finishedWork属性,即内存中已经构建好的离屏fiber树
  nextEffect = firstChild;
  commitXXXEffects_begin();

}

commitXXXEffects_begin中主要递归fiber树,执行该子阶段对应的处理函数,并找到第一个不包含该子阶段flags的fiberNode,执行该子阶段的complete方法

function commitBeforeMutationEffects_begin() {
  while (nextEffect !== null) {
    const fiber = nextEffect;
    const child = fiber.child;

    // 执行相关子阶段特有操作
    // ...
     
    // 找到第一个不包含该子阶段flags的fiberNode,执行该子阶段的complete方法
    if (
      (fiber.subtreeFlags & BeforeMutationMask) !== NoFlags &&
      child !== null
    ) {
      child.return = fiber;
      nextEffect = child;
    } else {
      commitBeforeMutationEffects_complete();
    }
  }
}

commitBeforeMutationEffects_complete中主要是归阶段,除了执行该子阶段对应的complete方法外,会开启sibling节点的begin流程,如果没有sibling节点,则执行父节点的complete方法。

function commitBeforeMutationEffects_complete() {
  while (nextEffect !== null) {
    const fiber = nextEffect;
    setCurrentDebugFiberInDEV(fiber);
    try {
      commitBeforeMutationEffectsOnFiber(fiber);
    } catch (error) {
      captureCommitPhaseError(fiber, fiber.return, error);
    }
    resetCurrentDebugFiberInDEV();

    const sibling = fiber.sibling;
    if (sibling !== null) {
      sibling.return = fiber.return;
      nextEffect = sibling;
      return;
    }

    nextEffect = fiber.return;
  }
}

before mutation

此子阶段的主要逻辑在commitBeforeMutationEffectsOnFiber中,主要作用为:

  • 对于Class Component,执行getSnapshotBeforeUpdate方法
  • 清空HostRoot挂载的内容,为mutation阶段做准备

mutation

此子阶段主要做dom相关操作,包括dom的增、删、改。

删除节点

当删除一个节点时,需要考虑:

  • 子树中所有组件的unmount逻辑
  • 子树中ref的卸载
  • 子树中Effect相关的hook(useEffectuseLayoutEffect)的destory回调

插入及移动节点

当插入一个节点时,需要执行:

  • 从当前fibernode向上遍历,找到第一个类型为HostComponentHostRootHostPortal的fibernode
  • 获取fibernode的稳定兄弟节点(不能被标记为Placement)
  • 如果存在稳定兄弟节点,会插入到兄弟节点之前,如果不存在,会插入到父节点的最后一个子节点的位置

更新节点

获取到fibernode上的stateNode属性,即真实dom节点,同时获取newProps(wipFiberNode.memoizedProps)、oldProps(current.memoizedProps)和updateQueue(),

  • style属性变化
  • innerHtml
  • 文本节点变化
  • 其他元素属性变化

更新到dom

Fiber Tree 切换

完成mutation阶段后会进行fiber 树的切换,即

root.current = finishedWork;

layout

主要执行

  • useLayoutEffect的回调
  • componentDidMount/Update的回调
  • 部分fiber节点上updateQueue的执行