react performUnitOfWork

6 阅读2分钟

如果说 workLoopConcurrent 是宏观上的“调度中心”,那么 performUnitOfWork 就是微观上的“具体执行者”。

它的核心任务是:处理当前这个 Fiber 节点,并找出下一个要处理的 Fiber 节点。


1. performUnitOfWork 的执行逻辑

performUnitOfWork 的工作可以形象地理解为**“深度优先遍历(DFS)”**。它将任务分为两个阶段:

第一阶段:“递” (Begin Phase)

调用 beginWork(unitOfWork)

  • 动作:从根节点开始,一层层向下探测子节点。

  • 任务

    • 如果是函数组件,执行它获取 children
    • 如果是类组件,调用 render 方法。
    • 对比旧的 Fiber 和新的 React Element,进行 Diff 算法
    • 打上副作用标记(Flags,如 PlacementUpdate)。
  • 结果:如果当前节点有“大儿子”(第一个子节点),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 的?