如果说 beginWork 是从上到下**“递”的过程,负责计算和标记,那么 completeWork 就是从下到上“归”**的过程,负责收尾、创建 DOM 实例和收集副作用(Effects)。
completeWork 是在 performUnitOfWork 内部,当一个 Fiber 节点没有子节点时(即到达叶子节点,或者子节点已经全部处理完毕)开始执行的。
1. completeWork 的核心职责
completeWork 主要执行以下三个关键任务:
A. 创建/更新 DOM 节点 (Mounting & Update)
-
如果是首次挂载 (Mounting) :
- 对于 Host Component(如
<div>、<span>等),React 会调用宿主环境(Host Environment)的 API 来创建真实的 DOM 元素。 - 例如,处理
<div id="app">时,会执行类似document.createElement('div')的操作。
- 对于 Host Component(如
-
如果是更新 (Updating) :
- React 会从
beginWork阶段收集到的 Payload 中获取需要更新的属性(如style、className等)。 - 它会将这些更新应用到已有的 DOM 节点上,但不会立即写入屏幕。
- React 会从
B. 构建 Effect List(副作用链表)
这是 completeWork 最重要的工作之一,也被称为收集阶段(Collection Phase) 。
- 当一个 Fiber 节点完成了它的工作(
completeWork)后,它会检查自己、它的子节点以及所有子节点已经收集到的 Effects。 - 它将这些带有副作用标记(如
Placement、Update、Deletion等)的 Fiber 节点,通过一个链表结构(称为effectList)挂载到父级 Fiber 节点上。 - 当这个收集过程一直回溯到 Fiber Root 时,整个 Root 上的
effectList就包含了一次更新中所有需要执行的 DOM 操作 和 生命周期/Hooks Effect。
C. 处理 Props 和 Ref
- Props:确保 DOM 元素上的 Props 能够正确应用,例如事件监听器等。
- Ref:对于包含
ref属性的组件,会在这个阶段将对应的 DOM 实例或组件实例引用附加到ref对象上,但这通常发生在 Commit 阶段。
2. completeWork 的流程拆解
这个过程是回溯性的,从最深的子节点开始向根节点累积。
代码段
graph TD
A[开始 completeWork current Fiber] --> B{节点类型?}
%% 宿主组件路径
B -- Host Component --> C1[创建或更新 DOM 元素]
C1 --> C2[处理 DOM Props]
C2 --> C3[将子 DOM 挂载到此 DOM 上 离屏构建]
%% 函数/类组件路径
B -- Class/Function Component --> D1[标记/处理 Layout Effects useLayoutEffect]
D1 --> D2[标记/处理 Passive Effects useEffect]
C3 --> E{检查是否有副作用 Flags?}
D2 --> E
E -- Yes --> F[将当前 Fiber 加入父级 Effect List]
E -- No --> G[跳过此节点]
F --> H[将子 Effect List 归并到父级 Effect List]
G --> H
H --> I[设置 nextUnitOfWork <br/>为兄弟节点或父节点]
I --> End([完成 completeWork])
3. 与 beginWork 的关系
| 特性 | beginWork(递) | completeWork(归) |
|---|---|---|
| 时机 | 从上往下遍历,遇到子节点就执行。 | 从下往上回溯,子节点处理完毕后执行。 |
| 核心任务 | 比较新旧 Fiber,执行组件函数,打上初步的副作用标记。 | 创建 DOM 节点,收集副作用,构建 Effect List。 |
| 产出物 | 决定下一个要处理的 Fiber 节点(通常是子节点)。 | 将 DOM 节点组装成离屏 DOM 树。 |
| 执行次数 | 每个 Fiber 节点执行一次。 | 每个 Fiber 节点执行一次。 |
4. 最终结果:Effect List
当 workLoopConcurrent 循环结束时,completeWork 的结果是一个完整的 Fiber Root 上的 Effect List。
- 这个链表只包含那些真正需要修改(新增、更新或删除)的 DOM 节点或具有 Effects 的组件。
- 在随后的 Commit 阶段,React 只需要遍历这个 Effect List,然后根据 Fiber 上的副作用标记(
Flags)依次执行对应的 DOM 操作,这个过程是非常高效的,因为避免了遍历整棵树。
通过 completeWork,React 成功在内存中完成了所有准备工作,实现了 “Render 阶段的可中断性” 和 “Commit 阶段的原子性” 。