一、引言
1.1 从 JSX 到 DOM 挂载
初次渲染是 “无旧 DOM 可复用” 的全新构建过程,整体流程可分为三步:
- 初始化 Fiber 根节点:React 从 ReactDOM.createRoot 或 ReactDOM.render 触发,创建根 Fiber 节点(FiberRoot),作为整个 Fiber 树的入口;
- 构建 workInProgress Fiber 树:通过 beginWork 自上而下遍历,为每个组件创建对应的 workInProgress Fiber 节点;再通过 completeWork 自下而上处理,生成 DOM 节点并绑定属性;
- 提交 DOM 挂载:构建完 workInProgress 树后,React 进入 “提交阶段”,将 workInProgress 树对应的 DOM 节点挂载到页面,同时将 current 树指向新树,完成渲染闭环。
1.2 beginWork 与 completeWork 的核心职责定位
beginWork 与 completeWork 是 Fiber 构建阶段的 “左右护法”,职责分工明确且互补:
- beginWork:负责 “向下探索”,从根 Fiber 开始,递归(迭代)为每个组件创建 workInProgress Fiber 节点,核心是 “生成 Fiber 结构、处理组件逻辑、确定子节点类型”;
- completeWork:负责 “向上收尾”,从叶子节点开始,逐步向上归并,核心是 “创建 DOM 节点、赋值 DOM 属性、收集副作用、关联父子 DOM”;
二者通过 “工作单元链表” 衔接:beginWork 处理完一个 Fiber 节点后,工作循环通过 performUnitOfWork 推进到下一个 Fiber(child/sibling/return),当遍历到叶子节点后,自动进入 completeWork 阶段。
二、beginWork:Fiber 树的向下构建与任务分解
beginWork 是 Fiber 构建阶段的 “前置引擎”,其核心逻辑是 “基于旧 Fiber 节点(若存在)或组件类型,创建新的 workInProgress Fiber 节点,并确定子节点的处理方式”。在初次渲染中,由于没有旧 current 树,beginWork 会直接基于组件类型初始化 Fiber 节点。
2.1 beginWork 的触发时机与入参说明
- 触发位置:工作循环在每个“工作单元(Fiber)”上调用
performUnitOfWork,其中第一步就是执行 beginWork(current, workInProgress, renderLanes),返回“下一个要处理的子 Fiber”。
- 入参与角色:
-
current:当前已提交树中的对应 Fiber(更新时存在;首屏挂载时可能为 null)。
-
workInProgress:本次渲染要构建的 Fiber 节点(WIP 树)。
-
renderLanes:当前渲染的优先级集合(决定是否跳过或继续展开)。
- 输出:返回子 Fiber(或
null),供工作循环继续向下深入。
2.2 不同类型 Fiber 节点的 beginWork 处理逻辑
2.2.1 重点节点速览
共性流程:根据 fiber.tag 分发到不同的更新函数,产出“下一步要渲染的子元素集合”,再调用“对比/构建子 Fiber(reconcileChildren)”。
常见类型与处理要点(简化版):
- FunctionComponent:
renderWithHooks 执行函数组件,跑 hooks,拿到 nextChildren;随后 reconcileChildren。
- ClassComponent:
updateClassComponent 处理实例与 processUpdateQueue,计算新 state,调用 render() 得到 nextChildren;随后 reconcileChildren。
- HostRoot(根):处理根上的更新队列(如
updateContainer 的结果),得到 nextChildren;随后 reconcileChildren。
- HostComponent(原生节点,如
div):不在此阶段做 DOM 变更;只根据 props.children 调用 reconcileChildren。
- HostText(文本):无子节点,通常返回
null。
- Suspense/Offscreen:根据数据可用性与可见性策略,决定展开主内容或 fallback,再
reconcileChildren。
- Memo/ForwardRef/SimpleMemoComponent:按其包装逻辑判断是否需要重渲染,否则可部分跳过。
简化示例(伪代码):
function beginWork(current, workInProgress, renderLanes) {
switch (workInProgress.tag) {
case FunctionComponent:
const Component = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps; // 未解析默认值的 props
// 解析函数组件的默认 props(若禁用默认 props 则直接使用 unresolvedProps)
const resolvedProps =
disableDefaultPropsExceptForClasses ||
workInProgress.elementType === Component
? unresolvedProps
: resolveDefaultPropsOnNonClassComponent(Component, unresolvedProps);
// 处理函数组件的更新/挂载(执行函数、处理 Hooks、生成子节点)
return updateFunctionComponent(
current,
workInProgress,
Component,
resolvedProps,
renderLanes
);
case ClassComponent:
const Component = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps; // 未解析默认值的 props
// 解析类组件的默认 props(static defaultProps)
const resolvedProps = resolveClassComponentProps(
Component,
unresolvedProps,
workInProgress.elementType === Component
);
return updateClassComponent(
current,
workInProgress,
Component,
resolvedProps,
renderLanes
);
case HostRoot: {
// 处理根节点的更新/挂载(管理全局状态、初始化上下文、协调子树)
return updateHostRoot(current, workInProgress, renderLanes);
}
case HostComponent: {
// 处理 DOM 元素的更新/挂载(对比 props、生成 DOM 属性、协调子节点)
return updateHostComponent(current, workInProgress, renderLanes);
}
case HostText:
return updateHostText(current, workInProgress);
// ... 其他类型(Suspense、Offscreen、Fragment、ContextProvider 等)
}
}
2.2.2 updateFunctionComponent 源码精读
在函数组件的 beginWork 阶段,updateFunctionComponent 负责“调用组件函数 + 驱动 Hooks 执行 + 产出子节点 + 协调子 Fiber”。它与 renderWithHooks 搭档工作:前者决定是否需要继续向下构建子树(以及是否可以直接跳过),后者负责在一次渲染里正确地运行 Hooks 并返回最新的子节点。
执行顺序总览:
- 读取上下文(
prepareToReadContext),为组件执行铺路;
- 调用
renderWithHooks,运行 Hooks 并拿到 nextChildren(JSX);
- 若是更新且未接收变化(
current !== null && !didReceiveUpdate),走“跳过路径”,复用旧子树;
didReceiveUpdate 来自更上层的变更判断(如 props/context 是否变化、更新优先级是否匹配)。当它为 false 时表示“本次无需对这个节点及其子树做工作,可直接重用旧 Fiber 和副作用信息”,于是会调用:
- 否则标记
PerformedWork 并调用 reconcileChildren,为子节点生成/更新子 Fiber。
renderWithHooks 的职责与关键点
- 渲染前重置当前 Fiber 的 Hooks 相关状态:
memoizedState、updateQueue、lanes,并设置全局渲染环境(currentlyRenderingFiber、renderLanes)。
- 根据是否首屏挂载选择调度器:
-
HooksDispatcherOnMount: 首次渲染,负责初始化如 useState 的初始值。
-
HooksDispatcherOnUpdate: 更新渲染,负责读取旧状态并应用队列里的更新。
- 运行组件函数得到
children;如果在执行过程中触发了“渲染阶段更新”(didScheduleRenderPhaseUpdateDuringThisPass),则使用 HooksDispatcherOnRerender 进行一次或多次“立即重渲染”,直到状态稳定或达到 RE_RENDER_LIMIT 上限。
- 这类更新的典型特征是“在函数组件执行过程中触发了自身状态的再次变更”,React 会尝试在同一渲染通道内收敛到稳定值,避免把不稳定的 UI 结果提交出去。
- 清理并完成 Hooks 的渲染(
finishRenderingHooks),将最终的 Hooks 状态和副作用信息落到 workInProgress 上,并返回稳定的 children 给上层做协调。
// 处理函数组件的更新逻辑
function updateFunctionComponent(
current: null | Fiber, // 旧 Fiber 节点(更新时存在,初次渲染为 null)
workInProgress: Fiber, // 当前正在构建的 Fiber 节点
Component: any, // 函数组件本身
nextProps: any, // 新的 props
renderLanes: Lanes // 当前渲染的优先级
) {
let context;
// 获取上下文(简化处理,省略 legacy 上下文兼容逻辑)
if (!disableLegacyContext) {
const unmaskedContext = getUnmaskedContext(workInProgress, Component, true);
context = getMaskedContext(workInProgress, unmaskedContext);
}
let nextChildren;
// 准备读取上下文
prepareToReadContext(workInProgress, renderLanes);
// 执行函数组件,通过 Hooks 处理状态和副作用,获取返回的子节点(JSX)
nextChildren = renderWithHooks(
current,
workInProgress,
Component,
nextProps,
context,
renderLanes
);
// 如果是更新且组件未发生变化,直接复用旧节点(跳过子树更新)
if (current !== null && !didReceiveUpdate) {
bailoutHooks(current, workInProgress, renderLanes);
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}
// 标记组件已执行工作
workInProgress.flags |= PerformedWork;
// 协调子节点(创建/更新子 Fiber 树)
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
// 返回第一个子 Fiber 节点,继续构建子树
return workInProgress.child;
}
// 函数组件核心渲染逻辑:执行组件并处理 Hooks
export function renderWithHooks(
current: Fiber | null, // 旧 Fiber 节点(更新时存在,初次渲染为 null)
workInProgress: Fiber, // 当前构建中的 Fiber 节点
Component: (p: Props, arg: SecondArg) => any, // 目标函数组件
props: Props, // 组件接收的新 props
secondArg: SecondArg, // 额外参数(如上下文,函数组件通常为 context)
nextRenderLanes: Lanes // 当前渲染优先级
): any {
// 1. 初始化渲染相关全局状态
renderLanes = nextRenderLanes; // 记录当前渲染优先级
currentlyRenderingFiber = workInProgress; // 标记当前正在渲染的 Fiber
// 2. 重置当前 Fiber 的 Hooks 相关状态(清空旧状态/队列)
workInProgress.memoizedState = null; // 清空 Hooks 状态存储
workInProgress.updateQueue = null; // 清空 Hooks 更新队列
workInProgress.lanes = NoLanes; // 重置优先级标记
// 3. 选择 Hooks 调度器(区分初次渲染/更新)
// - 初次渲染(current 为 null 或无旧 Hooks 状态):用挂载阶段调度器
// - 更新阶段(有旧 Hooks 状态):用更新阶段调度器
ReactSharedInternals.H =
current === null || current.memoizedState === null
? HooksDispatcherOnMount // 挂载时的 Hooks 实现(如 useState 初始化)
: HooksDispatcherOnUpdate; // 更新时的 Hooks 实现(如 useState 读取旧状态)
// 4. 执行函数组件,获取返回的子节点(JSX)
// 此时组件内的 Hooks(如 useState、useEffect)会通过上面的调度器执行
let children = Component(props, secondArg);
// 5. 处理渲染阶段更新(若组件内触发了 setState 等导致状态变化)
if (didScheduleRenderPhaseUpdateDuringThisPass) {
// 重新执行组件,确保状态稳定后再返回子节点
children = renderWithHooksAgain(
workInProgress,
Component,
props,
secondArg
);
}
// 6. 完成 Hooks 渲染:清理全局状态、记录最终 Hooks 信息
finishRenderingHooks(current, workInProgress, Component);
// 7. 返回组件渲染结果(子节点),用于后续 Fiber 子树协调
return children;
}
2.2.3 updateClassComponent 源码精读
在 beginWork 阶段,类组件通过 updateClassComponent 完成“实例构造/更新决策 + 调用 render 产出子节点 + 协调子 Fiber”。与函数组件不同,类组件不使用 Hooks;更新是否继续由 shouldUpdate 决策,来源于 mountClassInstance/resumeMountClassInstance/updateClassInstance。
执行顺序总览
- 上下文与错误边界预处理:
- 如果是 legacy 上下文提供者,先
pushLegacyContextProvider,避免上下文栈失配;
- 实例与更新决策:
-
instance === null 首次挂载:constructClassInstance → mountClassInstance → shouldUpdate = true;
-
current === null 恢复挂载(如挂起后恢复):resumeMountClassInstance → 返回 shouldUpdate;
- 否则常规更新:
updateClassInstance → 返回 shouldUpdate。
- 渲染与子树协调(
finishClassComponent):
- 若
!shouldUpdate && !didCaptureError,直接走“跳过路径”并可能失效上下文提供者;
- 否则调用
instance.render() 产出 nextChildren,根据是否处于错误恢复决定 forceUnmountCurrentAndReconcile 或常规 reconcileChildren;
- 最后将
instance.state 挂到 memoizedState 并在提供者场景下 invalidateContextProvider。
// 处理类组件的更新逻辑
function updateClassComponent(
current: Fiber | null, // 旧 Fiber 节点(更新时存在)
workInProgress: Fiber, // 当前构建中的 Fiber 节点
Component: any, // 类组件本身(构造函数)
nextProps: any, // 新的 props
renderLanes: Lanes // 当前渲染优先级
) {
let hasContext = false;
// 处理上下文提供者(若为上下文提供者组件)
if (isLegacyContextProvider(Component)) {
hasContext = true;
pushLegacyContextProvider(workInProgress); // 入栈上下文提供者
}
// 准备读取上下文
prepareToReadContext(workInProgress, renderLanes);
const instance = workInProgress.stateNode; // 类组件实例(this)
let shouldUpdate; // 是否需要更新
if (instance === null) {
// 1. 组件未实例化(初次挂载)
constructClassInstance(workInProgress, Component, nextProps); // 创建实例(new Component())
mountClassInstance(workInProgress, Component, nextProps, renderLanes); // 初始化实例(调用 componentWillMount 等)
shouldUpdate = true; // 初次挂载必更新
} else if (current === null) {
// 2. 恢复挂载(如从 Suspense 恢复)
shouldUpdate = resumeMountClassInstance(
workInProgress,
Component,
nextProps,
renderLanes
);
} else {
// 3. 组件已存在(更新阶段)
// updateClassInstance通过处理新旧 props/context、执行状态更新队列、调用相关生命周期方法(如 componentWillReceiveProps、
// shouldComponentUpdate),最终判断组件是否需要重新渲染,并更新实例的 props/state/context
shouldUpdate = updateClassInstance(
current,
workInProgress,
Component,
nextProps,
renderLanes
);
}
// 完成类组件处理,返回下一个工作单元
const nextUnitOfWork = finishClassComponent(
current,
workInProgress,
Component,
shouldUpdate,
hasContext,
renderLanes
);
return nextUnitOfWork;
}
// 完成类组件的渲染处理
function finishClassComponent(
current: Fiber | null,
workInProgress: Fiber,
Component: any,
shouldUpdate: boolean,
hasContext: boolean,
renderLanes: Lanes
) {
// 处理 ref 引用
markRef(current, workInProgress);
// 检查是否捕获到错误
const didCaptureError = (workInProgress.flags & DidCapture) !== NoFlags;
// 若无需更新且无错误捕获,直接复用旧节点(跳过子树更新)
if (!shouldUpdate && !didCaptureError) {
if (hasContext) {
invalidateContextProvider(workInProgress, Component, false); // 失效上下文
}
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}
const instance = workInProgress.stateNode; // 类组件实例
let nextChildren; // 渲染的子节点(JSX)
if (didCaptureError && !Component.getDerivedStateFromError) {
// 错误捕获且无错误处理方法时,子节点为空
nextChildren = null;
} else {
// 执行 render 方法获取子节点
nextChildren = instance.render();
}
// 标记组件已执行工作
workInProgress.flags |= PerformedWork;
// 协调子节点(创建/更新子 Fiber 树)
if (current !== null && didCaptureError) {
// 错误恢复场景:强制卸载旧子树并重新协调
forceUnmountCurrentAndReconcile(
current,
workInProgress,
nextChildren,
renderLanes
);
} else {
// 正常场景:协调子节点
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
}
// 保存当前状态到 Fiber 节点
workInProgress.memoizedState = instance.state;
if (hasContext) {
invalidateContextProvider(workInProgress, Component, true); // 更新上下文
}
// 返回第一个子 Fiber 节点,继续构建子树
return workInProgress.child;
}
2.3 子 Fiber 节点的创建与链表连接
两套对比器(核心区别在“有没有旧树可复用”):
- 首次挂载:
mountChildFibers(从 null 构建新子 Fiber 链)。
- 更新:
reconcileChildFibers(对比 current.child 链与新的 nextChildren:复用/插入/删除)。
链表结构与指针:
-
fiber.child:第一个子节点。
-
fiber.sibling:兄弟节点(单链向右)。
-
fiber.return:父节点(便于回溯完成与向上合并副作用)。
// JSX
// Fiber 链(简化)
MyPanel (return: parent)
└─ child → Title (return: MyPanel)
└─ sibling → Content (return: MyPanel)
关键点:beginWork 只“产出子树的结构与下一步任务”,不做 DOM;DOM 的创建与属性设置在 completeWork,插入/更新在“提交阶段”。
2.4 工作单元的分解与优先级处理
- 单元分解(DFS):
- 工作循环优先深入
child;child 完成后转向 sibling;最后回到 return。
- 每个 Fiber 的 begin/complete 构成一个“工作单元”,渲染可切片、可中断。
- 优先级(Lanes):
-
renderLanes 决定当前渲染的优先级集合;若该 Fiber 在此优先级下无待处理工作,可“跳过”(复用旧树)。
- 出口条件(简化理解):没有更高优先级的变更、
props/state 未变、更不需要上下文更新 → bailout,直接返回可能的 child 或 null。
- 概念串联:
- beginWork:决定“需要渲染哪些子元素”,并产生/对齐子 Fiber。
- completeWork:在向上回溯时创建宿主实例、收集副作用。
- 提交阶段:统一执行副作用(
commitPlacement/commitUpdate 等),把渲染结果应用到 DOM。
// 例子:点击后新增一个 <li>
function List({items}) {
return (
<ul>
{items.map((i) => (
<li>{i}</li>
))}
</ul>
);
}
// 点击 setState 入队 → 渲染阶段:
// beginWork(List) → nextChildren = [..., <li>]
// reconcileChildren 对比老的 <li> 列表:复用旧的、创建新的 Fiber
// completeWork(new <li>):准备 DOM 实例与属性
// 提交阶段:commitPlacement(new <li>) 插入到真实 DOM
三、completeWork:Fiber 树的向上归并与 DOM 生成
当 beginWork 遍历到叶子节点(无 child 的 Fiber 节点)后,performUnitOfWork 会切换到 completeWork 阶段。completeWork 从叶子节点开始向上归并,核心是 “将 Fiber 节点转换为实际 DOM,并完成挂载前的准备”。
3.1 completeWork 的触发时机与执行条件
- 触发时机:工作循环在某个 Fiber 的子树已展开完毕、
beginWork 返回 null 后,会回溯到该 Fiber 并执行 completeWork(current, workInProgress, renderLanes)。这是一段“向上归并”的过程。
- 作用概览:
- 为宿主节点(HostComponent/HostText)创建或对齐实例(不插入 DOM)。
- 为“更新”生成变更负载(payload),以便提交阶段执行。
- 向上合并(bubble)子树的副作用标志(
subtreeFlags),为提交阶段收敛出一条待执行的效果链。
示意(伪代码):
function completeUnitOfWork(unitOfWork: Fiber): void {
let completedWork: Fiber = unitOfWork;
do {
const current = completedWork.alternate;
const returnFiber = completedWork.return;
let next;
// 执行完成工作的核心逻辑
next = completeWork(current, completedWork, entangledRenderLanes);
// 若生成新工作单元,切换到新工作单元并返回
if (next !== null) {
workInProgress = next;
return;
}
// 若有兄弟节点,切换到兄弟节点并返回
const siblingFiber = completedWork.sibling;
if (siblingFiber !== null) {
workInProgress = siblingFiber;
return;
}
// 否则返回父节点继续处理
completedWork = returnFiber;
workInProgress = completedWork;
} while (completedWork !== null);
}
3.2 不同类型 Fiber 节点的 completeWork 处理逻辑
与 beginWork 类似,completeWork 也会根据 Fiber 节点的 tag 执行差异化逻辑,核心类型如下:
- HostComponent(如
div):
- 首次挂载:创建宿主实例
createInstance(type, props, rootContainer, hostContext),appendAllChildren(instance, wip) 把已创建的子实例挂到该实例上(仍在内存结构里),然后 setInitialProperties(instance, props) 设置初始属性。
- 更新:对比新旧 props,
prepareUpdate(instance, type, oldProps, newProps, rootContainer, hostContext) 返回变更负载;若非空,给 Fiber 标记 Update 并挂载负载,留给提交阶段执行。
- HostText(文本节点):
- 首次挂载:
createTextInstance(text, rootContainer, hostContext) 创建文本实例,挂到 fiber.stateNode。
- 更新:若文本变了,标记
Update,提交阶段会调用 commitHostTextUpdate 实际更新内容。
- FunctionComponent / Fragment / Context 等非宿主节点:
- 不创建 DOM 实例;completeWork 主要做“副作用合并”和
Ref 变更判断(若 ref 变化会标记 Ref)。
- ClassComponent:
- 不直接创建 DOM;常在
completeWork 阶段标记 Ref(实例 ref 变化)与合并子树效果。
- Suspense / Offscreen:
- 根据可见性/回退策略在 begin 阶段已决定子树展开;complete 阶段继续合并 flags,并可能标记
Visibility 相关效果。
3.3 DOM 节点的创建、属性赋值与子节点挂载
核心要点:
- completeWork“生成/准备”宿主实例与属性,不做真实 DOM 插入;真实插入发生在提交阶段的
commitPlacement。
- 子节点挂载通过
appendAllChildren(instance, wip) 把子实例树挂到当前实例的内部结构(即 stateNode 的子列表),为后续 commitPlacement 一次性插入打好基础。
// (挂载 vs 更新)
function completeWork(current, wip) {
switch (wip.tag) {
case ActivityComponent:
case LazyComponent:
case SimpleMemoComponent:
case FunctionComponent:
case ForwardRef:
case Fragment:
case Mode:
case Profiler:
case ContextConsumer:
case MemoComponent:
// 这些类型的组件不需要创建DOM节点
// 冒泡当前节点的属性(如 subtreeFlags、lanes)到父节点
bubbleProperties(workInProgress);
return null; // 无后续处理,返回 null
case HostComponent: {
// ...首次客户端渲染逻辑
// 更新宿主容器(如 DOM 容器)的状态
updateHostContainer(current, workInProgress);
// 冒泡属性到父节点(根节点无父节点,此处主要是处理自身 subtreeFlags)
bubbleProperties(workInProgress);
return;
}
case HostText: {
const newText = wip.pendingProps;
if (current && workInProgress.stateNode != null) {
const oldText = current.memoizedProps; // 上一次渲染的文本内容
// 调用文本节点更新逻辑(对比新旧文本,生成更新队列)
updateHostText(current, workInProgress, oldText, newText);
} else {
// 客户端挂载:创建文本实例(如 DOM 中的 Text 节点)
const rootContainerInstance = getRootHostContainer();
const currentHostContext = getHostContext();
markCloned(workInProgress);
// 创建文本实例(如 DOM 中的 Text 节点)
workInProgress.stateNode = createTextInstance(
newText,
rootContainerInstance,
currentHostContext,
workInProgress
);
}
bubbleProperties(workInProgress);
return;
}
// ... 其他类型以“合并副作用”为主
}
}
3.4 副作用的收集与标记
Flags(部分常见):
-
Placement:表示需要插入(新增节点)。
-
Update:属性或文本变更,提交阶段读取 Fiber 的变更负载执行。
-
Deletion:删除节点。
-
Ref:ref 变更,提交阶段会附加/清理 ref。
-
Visibility / Passive:可见性与副作用相关标记。
合并与传播:
- 每个 Fiber 在 complete 阶段会把自身与子树的 flags 合并到
subtreeFlags,以便根在提交阶段一次性遍历需要执行的效果。
合并示意:
// 功能:在 Fiber 树归并阶段,向上汇总子节点的优先级、副作用等关键信息
function bubbleProperties(completedWork: Fiber) {
// 判断是否"跳过更新"(子树无变化):存在旧节点且新旧子节点引用相同
const didBailout =
completedWork.alternate !== null &&
completedWork.alternate.child === completedWork.child;
let newChildLanes: Lanes = NoLanes; // 合并后的子树优先级
let subtreeFlags = NoFlags; // 合并后的子树副作用标记
if (!didBailout) {
// 【子树有更新】:全量收集子节点信息
let child = completedWork.child;
while (child !== null) {
// 合并子节点自身及子树的优先级
newChildLanes = mergeLanes(
newChildLanes,
mergeLanes(child.lanes, child.childLanes)
);
// 合并子节点自身及子树的所有副作用
subtreeFlags |= child.subtreeFlags | child.flags;
// 修正子节点的父引用,确保链表关系正确
child.return = completedWork;
child = child.sibling; // 遍历下一个兄弟节点
}
// 将合并的子树副作用标记到当前节点
completedWork.subtreeFlags |= subtreeFlags;
} else {
// 【子树无更新】:仅收集静态信息(过滤动态副作用)
let child = completedWork.child;
while (child !== null) {
// 合并子节点自身及子树的优先级(未处理的优先级仍需传递)
newChildLanes = mergeLanes(
newChildLanes,
mergeLanes(child.lanes, child.childLanes)
);
// 仅合并静态副作用(如静态DOM属性,动态副作用无需传递)
subtreeFlags |=
(child.subtreeFlags & StaticMask) | (child.flags & StaticMask);
// 修正子节点的父引用
child.return = completedWork;
child = child.sibling;
}
completedWork.subtreeFlags |= subtreeFlags;
}
// 保存合并后的子树优先级到当前节点
completedWork.childLanes = newChildLanes;
// 返回是否跳过更新的标志,供上层节点判断
return didBailout;
}
function TextExample() {
const [text, setText] = useState("hello");
useEffect(() => {
setTimeout(() => setText("world"), 100);
}, []);
return <div>{text}</div>;
}
// 1. 初次挂载:生成 `HostText("hello")`,提交后运行 `useEffect`(被动副作用)。
// 2. 更新调度:`setTimeout` 触发 `setText("world")`,通过 `scheduleUpdateOnFiber` 为根打上待处理的 `Lane` 并触发下一次渲染。
// 3. render 阶段:
// - `BeginWork` 进入 `HostText` 的更新路径。
// - `CompleteWork` 的 `case HostText` 中调用 `updateHostText(current, workInProgress, oldText, newText)`。
// - 若 `oldText !== newText`,执行 `markUpdate(workInProgress)`,在该 Fiber 的 `flags` 中设置 `Update`。
// - 随后 `bubbleProperties` 汇总子树标志到父节点的 `subtreeFlags`,便于提交阶段快速定位变化。
// 4. 提交阶段(mutation effects):
// - 扫描到该 `HostText` 带有 `Update`,调用 `commitHostTextUpdate(stateNode, oldText, newText)` 实际更新 DOM 文本为 "world"。
结论(把 complete 与提交阶段区分清楚)
- completeWork 是“向上归并与准备”:创建/对齐宿主实例、设置初始属性或生成变更负载、合并副作用标记。
- 真实 DOM 变更(插入/更新/删除)在提交阶段完成:如
commitPlacement、commitHostTextUpdate 等。
- 这保证渲染阶段能被时间切片与优先级打断,而提交阶段在确定的效果列表上一次性执行,提升稳定性与可控性。
四、beginWork 与 completeWork 的协同机制
beginWork 的 “向下构建” 与 completeWork 的 “向上归并” 并非独立流程,而是通过 “工作单元链表” 和 “双缓存机制” 紧密协同,共同完成初次渲染的 Fiber 树构建与 DOM 生成。
4.1 自上而下构建与自下而上归并的流程闭环
流程主线:
- 向下构建(beginWork):根据
fiber.tag 选择处理器,计算 nextChildren,并通过 mountChildFibers/reconcileChildFibers 生成/复用子 Fiber 链,返回 fiber.child 继续深入。
- 向上归并(completeWork):当子树展开完毕(
beginWork 返回 null)回溯到父节点,创建/对齐宿主实例(HostComponent/HostText)、准备更新负载,并合并副作用标记(bubbleProperties)。
- 提交阶段(commit):在统一的效果列表上执行宿主副作用,将变更应用到 DOM(如
commitPlacement、commitHostTextUpdate)。
// 简化伪码
function performUnitOfWork(wip) {
// 向下:展开当前节点的子树
const next = beginWork(wip.alternate, wip, renderLanes);
wip.memoizedProps = wip.pendingProps;
// 有子,继续向下;无子,开始向上
if (next !== null) return next;
// 向上:准备实例/负载并合并副作用
do {
completeWork(wip.alternate, wip, renderLanes);
bubbleProperties(wip); // 合并 flags / subtreeFlags
if (wip.sibling !== null) return wip.sibling;
wip = wip.return;
} while (wip !== null);
// 根收敛后,进入提交阶段
return null;
}
4.2 双缓存 Fiber 树切换中的职责配合
- 双缓存(current ↔ workInProgress):
- 渲染阶段构建的是 WIP 树(指针
workInProgress.alternate === current);旧树 current 仍代表“已提交”的 UI。
- commit 阶段将
root.current 切换为本次完成的 finishedWork(WIP 树),使其成为新的“已提交”树。
- 职责分工:
- beginWork:读取
current 的已有信息(如 memoizedProps/state),决定是否复用(bailout)或生成新的子 Fiber。
- completeWork:在 WIP 节点上创建/对齐宿主实例、设置初始属性或生成更新负载,并合并副作用。
- commit:读取 WIP 树上的 Flags/负载,进行 DOM 插入/更新/删除,并完成 ref 和 effect 的附加/清理。
- 插入位置明确:
- 宿主实例的“创建与属性准备”在 complete 阶段完成;
- 真实插入 DOM 的操作在提交阶段
commitPlacement 执行。
五、最后
Fiber 架构下的渲染,本质是通过 beginWork 与 completeWork 的双向协作,完成从组件逻辑到 DOM 实例的高效转化 —— 二者如同 “分工明确的工程队”:beginWork 负责 “规划蓝图”(构建 Fiber 树结构、分解渲染任务、处理组件逻辑),completeWork 负责 “落地施工”(创建 DOM 实例、准备属性与更新负载、收集副作用),最终通过双缓存机制与提交阶段,实现视图的稳定挂载。
理解 beginWork 与 completeWork 的核心分工后,可进一步拆解 React 渲染的关键细节,形成完整的知识体系:
- 子 Fiber 构建的核心:reconcileChildren 逻辑:深入 mountChildFibers(初次挂载)与 reconcileChildFibers(更新)的内部实现,理解 React 如何通过 “key 比对”“类型判断” 实现子节点的复用、插入与删除,掌握 Diff 算法的核心规则;
- 宿主节点的差异化处理:聚焦 HostComponent(原生 DOM 节点)与 HostText(文本节点)的完整生命周期,包括 createInstance/createTextInstance 的 DOM 创建细节、setInitialProperties 的属性初始化逻辑,以及更新时 prepareUpdate 的变更计算规则;
- 提交阶段的副作用执行:从 completeWork 收集的副作用标记出发,学习提交阶段如何通过 commitMutationEffects(执行 DOM 变更)、commitLayoutEffects(执行布局相关副作用)等流程,将 workInProgress 树的准备结果最终应用到页面;