Appearance
5.2 Fiber 树的构建:beginWork 与 completeWork
在上一节中,我们区分了 ReactElement 和 Fiber。ReactElement 是静态的蓝图,而 Fiber 则是动态的工作单元。那么,React 是如何根据 ReactElement 的变化,构建出新的 Fiber 树(我们称之为 work-in-progress 树)的呢?答案就在 Render 阶段的核心——beginWork 和 completeWork 这两个函数中。
Render 阶段本质上是一个深度优先遍历 Fiber 树的过程,这个过程被巧妙地分为了两个阶段:
- “递”阶段 (Begin Phase):由
beginWork函数主导,从父节点走向子节点,处理每一个需要更新的 Fiber。 - “归”阶段 (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中最重要的优化策略。如果一个组件的props、state和context都没有改变,React 会认为这个组件及其子树都没有发生变化,从而跳过对它们的处理。这是 React 高效更新的关键所在,避免了不必要的render和diff。 - 任务分发:
beginWork内部通过一个巨大的switch语句,根据Fiber的tag(如FunctionComponent,ClassComponent,HostComponent等),将工作分发给不同类型的更新函数(如updateFunctionComponent)。这些函数会负责调用组件的render方法或函数本身,获取最新的children(ReactElement数组),然后调用reconcileChildren来生成新的子 Fiber。
beginWork 执行完毕后,会返回下一个要处理的 Fiber 节点,即 workInProgress 的第一个子节点。如果该节点没有子节点,则返回 null。
completeWork:向上回溯与构建 DOM
当一个 Fiber 节点的所有子节点(以及所有后代节点)都完成了 beginWork 和 completeWork 后,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 的关键工作包括:
- 创建/更新 DOM 实例:对于
HostComponent(如<div>,<span>),completeWork会调用createInstance来创建真实的 DOM 节点,并将其保存在fiber.stateNode上。如果是更新,则会调用diffProperties计算出需要变更的属性,生成一个updatePayload,并标记Update副作用。 - 处理子节点:调用
appendAllChildren将当前 Fiber 的所有子孙HostComponent的 DOM 节点,附加到自己创建的 DOM 节点上。这就是为什么completeWork必须在所有子节点都完成后才能执行的原因。 - 冒泡副作用:
completeWork会检查当前 Fiber 自身以及其子树(通过child.subtreeFlags)是否有副作用。它会将这些副作用flags冒泡到父节点的subtreeFlags上。这样,当遍历回到根节点时,根节点的subtreeFlags就包含了整棵树所有需要执行的副作用。
总结
beginWork 和 completeWork 共同构成了 React Render 阶段的核心循环。
beginWork像一个“探索者”,自上而下,识别出需要做的工作,并为子节点创建新的Fiber。completeWork像一个“建筑师”,自下而上,在“探索者”完成一个分支的探索后,开始构建真实的 DOM 结构,并把需要进行的 DOM 操作(如创建、更新、删除)以副作用flags的形式记录下来。
当 workLoop 完成对根节点的 completeWork 后,整个 work-in-progress 树就构建完毕,并且所有需要执行的 DOM 操作都以副作用链表的形式挂载到了 Fiber 树上。接下来,React 就会进入 Commit 阶段,执行这些副作用,将变化真实地渲染到屏幕上。