1. Commit 阶段的核心约束与设计哲学
在 React 的更新流程中,Commit 阶段是真正将虚拟 DOM 变更应用到真实 DOM 的关键环节。与可以被打断的 Render 阶段不同,Commit 阶段必须遵循三个铁律:
-
同步执行:Commit 必须一次性完成,不能像 Render 阶段那样可以被打断和恢复。这就像数据库事务的原子性——要么全部完成,要么全部不执行。
-
严格有序:副作用必须按照确定的顺序执行。React 将 Commit 细分为 BeforeMutation、Mutation、Layout 和 Passive 四个子阶段,每个阶段有明确的职责边界。
-
高效剪枝:Commit 需要遍历 Fiber 树,但必须避免无意义的遍历。React 通过 Flags 系统和 StaticMask 机制实现高效剪枝。
提示:理解这些约束是读懂 Commit 阶段源码的关键。React 的所有实现细节都是为了满足这些核心要求。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. Flags 系统:声明式副作用管理
2.1 flags 与 subtreeFlags 的协同工作
每个 Fiber 节点都有两个关键的位图属性:
javascript复制fiber.flags // 当前节点的副作用标记
fiber.subtreeFlags // 子树中存在的副作用标记
这种设计带来了显著的性能优化:
-
当遍历到某个节点时,首先检查
subtreeFlags & Mask:- 如果结果为 0,说明整棵子树都没有相关副作用,可以直接跳过
- 否则才需要继续深入遍历
-
在 completeWork 阶段,React 会通过
bubbleProperties方法自底向上冒泡这些标记:
javascript复制// 正常情况下的冒泡
subtreeFlags |= child.subtreeFlags
subtreeFlags |= child.flags
// bailout 时的特殊处理
if (didBailout) {
subtreeFlags |= child.subtreeFlags & StaticMask
subtreeFlags |= child.flags & StaticMask
}
2.2 StaticMask:长期副作用的特殊处理
StaticMask 用于标记那些"长期有效"的副作用,即使当前渲染没有更新也需要处理。典型的例子包括:
- 使用了
useEffect的函数组件,在卸载时需要执行 cleanup - Portal 类型的组件,在卸载时需要特殊处理
- 使用了 ref 的组件
StaticMask 的定义如下:
javascript复制export const StaticMask =
LayoutStatic |
PassiveStatic |
RefStatic |
MaySuspendCommit |
ViewTransitionStatic |
ViewTransitionNamedStatic |
PortalStatic
这种设计确保了即使组件 bailout(跳过渲染),必要的清理工作仍能正确执行。
3. Commit 阶段的状态机模型
3.1 整体流程与阶段划分
Commit 阶段的执行流程可以看作一个状态机:
code复制commitRootWhenReady
