如果说 workLoopConcurrent 是宏观上的“调度中心”,那么 performUnitOfWork 就是微观上的“具体执行者”。
它的核心任务是:处理当前这个 Fiber 节点,并找出下一个要处理的 Fiber 节点。
1. performUnitOfWork 的执行逻辑
performUnitOfWork 的工作可以形象地理解为**“深度优先遍历(DFS)”**。它将任务分为两个阶段:
第一阶段:“递” (Begin Phase)
调用 beginWork(unitOfWork)。
-
动作:从根节点开始,一层层向下探测子节点。
-
任务:
- 如果是函数组件,执行它获取
children。 - 如果是类组件,调用
render方法。 - 对比旧的 Fiber 和新的 React Element,进行 Diff 算法。
- 打上副作用标记(Flags,如
Placement或Update)。
- 如果是函数组件,执行它获取
-
结果:如果当前节点有“大儿子”(第一个子节点),
beginWork会返回这个子节点,作为下一个工作单元。
第二阶段:“归” (Complete Phase)
如果没有子节点了(到达了叶子节点),则调用 completeUnitOfWork(unitOfWork)。
-
动作:从当前节点开始往回走。
-
任务:
- 调用
completeWork:创建或更新真实 DOM 实例,处理 Props,收集副作用。 - 寻找兄弟:看当前节点有没有“弟弟”(sibling)。如果有,就把“弟弟”交给
workLoop去处理。 - 回溯父亲:如果既没有孩子也没有弟弟,就回到“父亲”节点,标记父亲已完成,继续找父亲的弟弟。
- 调用
-
终点:直到回到根节点(Root),整个渲染阶段(Render Phase)结束。
2. Mermaid 流程图
代码段
graph TD
Start([开始 performUnitOfWork]) --> BeginWork[执行 beginWork: <br/>计算 State, Diff 算法, 创建子 Fiber]
BeginWork --> HasChild{是否有子节点?}
%% 递阶段
HasChild -- Yes --> SetNextChild[将 workInProgress 指向子节点]
SetNextChild --> End([结束当前 UnitOfWork, 返回继续循环])
%% 归阶段
HasChild -- No --> CompleteUnit[执行 completeUnitOfWork]
CompleteUnit --> CompleteWork[执行 completeWork: <br/>创建 DOM, 收集 Effect Flags]
CompleteWork --> HasSibling{是否有兄弟节点?}
HasSibling -- Yes --> SetNextSibling[将 workInProgress 指向兄弟节点]
SetNextSibling --> End
HasSibling -- No --> HasParent{是否有父节点?}
HasParent -- Yes --> MoveToParent[移动到父节点]
MoveToParent --> CompleteWork
HasParent -- No --> RootComplete[整棵 Fiber 树处理完成]
RootComplete --> SetWIPNull[workInProgress = null]
SetWIPNull --> End
3. 核心细节补充
为什么它能“中断”?
你会发现 performUnitOfWork 本身并不包含循环(循环在 workLoop 里)。它只负责**“动一小步”**。
- 它每处理完一个 Fiber,都会把指针移到下一个位置。
workLoop在每次调用performUnitOfWork之前都会问shouldYield()。- 如果时间到了,
workLoop退出,但此时全局变量workInProgress正停在刚才performUnitOfWork算出来的“下一个节点”上。这就实现了进度保存。
离屏 DOM 构建
在 completeWork 阶段,React 会在内存中构建好 DOM 树。如果是新挂载的组件,它会把子 DOM 节点插入到当前 DOM 节点中。这意味着当整个 workLoop 结束时,React 已经在内存里准备好了一棵完整的、带更新的 DOM 树,下一步只需在 commitRoot 阶段一次性“挂”到屏幕上即可。
总结
- beginWork:向下找,找儿子。主要做逻辑计算和 Diff。
- completeWork:向上找,找兄弟/父亲。主要做 DOM 节点创建和结果收集。
您想进一步了解 beginWork 内部是如何进行 Diff 算法比对的,还是想看看 completeWork 是如何收集 Effect 的?