Skip to content

5.2 Fiber 树的构建:beginWork 与 completeWork

在上一节中,我们区分了 ReactElementFiberReactElement 是静态的蓝图,而 Fiber 则是动态的工作单元。那么,React 是如何根据 ReactElement 的变化,构建出新的 Fiber 树(我们称之为 work-in-progress 树)的呢?答案就在 Render 阶段的核心——beginWorkcompleteWork 这两个函数中。

Render 阶段本质上是一个深度优先遍历 Fiber 树的过程,这个过程被巧妙地分为了两个阶段:

  1. “递”阶段 (Begin Phase):由 beginWork 函数主导,从父节点走向子节点,处理每一个需要更新的 Fiber。
  2. “归”阶段 (Complete Phase):由 completeWork 函数主导,当一个节点的所有子节点都处理完毕后,返回处理该节点自身,自下而上。

这个过程就像是在遍历一个树形结构,beginWork 负责向下探索,completeWork 负责在探索完一个分支后向上回溯。

beginWork:向下探索与创建子 Fiber

beginWork 是 Render 阶段的入口,它接收 current(当前屏幕上对应的 Fiber)和 workInProgress(正在构建的 Fiber)作为参数,它的核心职责是:计算出当前 Fiber 的更新,并创建它的直接子 Fiber

javascript
// react/packages/react-reconciler/src/ReactFiberWorkLoop.js

function beginWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderLanes: Lanes,
): Fiber | null {
  // 1. 优化路径:Bailout
  if (current !== null) {
    const oldProps = current.memoizedProps;
    const newProps = workInProgress.pendingProps;

    if (
      oldProps !== newProps ||
      hasLegacyContextChanged()
      // ... 其他检查
    ) {
      didReceiveUpdate = true;
    } else {
      // 检查是否有计划中的更新或 context 变化
      const hasScheduledUpdateOrContext = checkScheduledUpdateOrContext(
        current,
        renderLanes,
      );
      if (!hasScheduledUpdateOrContext && (workInProgress.flags & DidCapture) === NoFlags) {
        // 如果 props、state、context 都没有变化,并且没有挂起的更新,就可以跳过这个组件的 work
        didReceiveUpdate = false;
        return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
      }
    }
  } else {
    didReceiveUpdate = false;
  }

  // 2. 根据 workInProgress.tag 执行不同组件的更新逻辑
  workInProgress.lanes = NoLanes;
  switch (workInProgress.tag) {
    case IndeterminateComponent:
      // ...
    case LazyComponent:
      // ...
    case FunctionComponent: {
      const Component = workInProgress.type;
      const unresolvedProps = workInProgress.pendingProps;
      const resolvedProps =
        workInProgress.elementType === Component
          ? unresolvedProps
          : resolveDefaultProps(Component, unresolvedProps);
      return updateFunctionComponent(
        current,
        workInProgress,
        Component,
        resolvedProps,
        renderLanes,
      );
    }
    case ClassComponent:
      // ...
    case HostRoot:
      // ...
    case HostComponent:
      return updateHostComponent(current, workInProgress, renderLanes);
    // ... 其他 tag 类型
  }
}

beginWork 的设计体现了 React 的性能优化哲学:

  • Bailout ( bailoutOnAlreadyFinishedWork ):这是 beginWork 中最重要的优化策略。如果一个组件的 propsstatecontext 都没有改变,React 会认为这个组件及其子树都没有发生变化,从而跳过对它们的处理。这是 React 高效更新的关键所在,避免了不必要的 renderdiff
  • 任务分发beginWork 内部通过一个巨大的 switch 语句,根据 Fibertag(如 FunctionComponent, ClassComponent, HostComponent 等),将工作分发给不同类型的更新函数(如 updateFunctionComponent)。这些函数会负责调用组件的 render 方法或函数本身,获取最新的 childrenReactElement 数组),然后调用 reconcileChildren 来生成新的子 Fiber。

beginWork 执行完毕后,会返回下一个要处理的 Fiber 节点,即 workInProgress 的第一个子节点。如果该节点没有子节点,则返回 null

completeWork:向上回溯与构建 DOM

当一个 Fiber 节点的所有子节点(以及所有后代节点)都完成了 beginWorkcompleteWork 后,workLoop 就会为这个 Fiber 节点执行 completeWork

completeWork 的核心职责是:为 Fiber 节点生成对应的 DOM 实例,并将副作用(Side Effects)冒泡到父节点

javascript
// react/packages/react-reconciler/src/ReactFiberCompleteWork.js

function completeWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderLanes: Lanes,
): Fiber | null {
  const newProps = workInProgress.pendingProps;

  switch (workInProgress.tag) {
    case FunctionComponent:
    case ForwardRef:
    case MemoComponent:
    case SimpleMemoComponent:
      // 这些组件没有自己的 DOM 节点,直接冒泡属性
      bubbleProperties(workInProgress);
      return null;
    case HostComponent: { // 原生 DOM 节点
      popHostContext(workInProgress);
      const type = workInProgress.type;

      if (current !== null && workInProgress.stateNode != null) {
        // 更新已存在的 DOM 节点
        const oldProps = current.memoizedProps;
        const instance = workInProgress.stateNode;
        const updatePayload = diffProperties(instance, type, oldProps, newProps);

        workInProgress.updateQueue = (updatePayload: any);
        if (updatePayload) {
          // 如果有需要更新的属性,标记 Update 副作用
          markUpdate(workInProgress);
        }
      } else {
        // 创建新的 DOM 节点
        const instance = createInstance(type, newProps, rootContainerInstance, hostContext, workInProgress);
        // 将所有子节点的 DOM 实例附加到当前创建的 instance 上
        appendAllChildren(instance, workInProgress, false, false);
        workInProgress.stateNode = instance;

        // 为新创建的 DOM 节点设置初始属性
        if (finalizeInitialChildren(instance, type, newProps, rootContainerInstance)) {
          markUpdate(workInProgress);
        }
      }
      bubbleProperties(workInProgress);
      return null;
    }
    // ... 其他 tag 类型
  }
  return null;
}

completeWork 的关键工作包括:

  1. 创建/更新 DOM 实例:对于 HostComponent(如 <div>, <span>),completeWork 会调用 createInstance 来创建真实的 DOM 节点,并将其保存在 fiber.stateNode 上。如果是更新,则会调用 diffProperties 计算出需要变更的属性,生成一个 updatePayload,并标记 Update 副作用。
  2. 处理子节点:调用 appendAllChildren 将当前 Fiber 的所有子孙 HostComponent 的 DOM 节点,附加到自己创建的 DOM 节点上。这就是为什么 completeWork 必须在所有子节点都完成后才能执行的原因。
  3. 冒泡副作用completeWork 会检查当前 Fiber 自身以及其子树(通过 child.subtreeFlags)是否有副作用。它会将这些副作用 flags 冒泡到父节点的 subtreeFlags 上。这样,当遍历回到根节点时,根节点的 subtreeFlags 就包含了整棵树所有需要执行的副作用。

总结

beginWorkcompleteWork 共同构成了 React Render 阶段的核心循环。

  • beginWork 像一个“探索者”,自上而下,识别出需要做的工作,并为子节点创建新的 Fiber
  • completeWork 像一个“建筑师”,自下而上,在“探索者”完成一个分支的探索后,开始构建真实的 DOM 结构,并把需要进行的 DOM 操作(如创建、更新、删除)以副作用 flags 的形式记录下来。

workLoop 完成对根节点的 completeWork 后,整个 work-in-progress 树就构建完毕,并且所有需要执行的 DOM 操作都以副作用链表的形式挂载到了 Fiber 树上。接下来,React 就会进入 Commit 阶段,执行这些副作用,将变化真实地渲染到屏幕上。

Last updated: