Appearance
DOM Mutations 阶段详解
在 DOM Mutations 阶段,React 将会把在 Render Phase 计算出来的所有变更(新增、更新、删除)真实地应用到宿主环境的 DOM 树上。这个阶段的操作是同步执行的,并且会阻塞浏览器的渲染,直到所有变更完成。
内部流程
commitRoot
调用commitMutationEffects
:- 这是 Mutation 阶段的入口。
初始化阶段状态:
- 设置一些全局变量,如
inProgressLanes
和inProgressRoot
,用于跟踪当前的提交上下文。
- 设置一些全局变量,如
调用
commitMutationEffectsOnFiber
开始处理 Effect List:- 这是实际执行 DOM 操作的核心循环的起点。它会从
finishedWork
(根 Fiber 或发生更新的子树的根) 开始,并递归地处理 effect list 中的 Fiber 节点。
- 这是实际执行 DOM 操作的核心循环的起点。它会从
commitMutationEffectsOnFiber
遍历 Effect List (针对每个带有副作用的 Fiber):- 获取当前 Fiber 信息:包括其类型 (
tag
),旧 Fiber (alternate
) 和副作用标记 (flags
)。 - 判断 Fiber 类型:根据不同的 Fiber 类型执行不同的操作。
- 调用
recursivelyTraverseMutationEffects
处理子节点和删除 (这是一个关键的递归步骤,通常先于当前节点的插入和更新):- 处理当前 Fiber 的
Deletions
列表 (通过commitDeletionEffects
):- 递归卸载被删除子树的 Effects:对于每个要删除的 Fiber,会递归遍历其子树,执行清理工作,例如解绑
ref
,调用类组件的componentWillUnmount
方法,以及执行 Hooks (如useEffect
,useLayoutEffect
) 的清理函数。 - 从 DOM 中移除节点:将被删除 Fiber 对应的真实 DOM 节点从其父节点中移除。
- 递归卸载被删除子树的 Effects:对于每个要删除的 Fiber,会递归遍历其子树,执行清理工作,例如解绑
- 递归调用
commitMutationEffectsOnFiber
处理子 Fiber:完成删除后,继续向下递归处理当前 Fiber 的子节点中带有MutationMask
的 Fiber。
- 处理当前 Fiber 的
- 调用
commitReconciliationEffects
处理当前 Fiber 的插入:- 检查
Placement
标记:如果 Fiber 带有Placement
标记,意味着它是一个新插入的节点。 commitHostPlacement
:插入 DOM 节点:将该 Fiber 对应的真实 DOM 节点插入到父 DOM 节点中的正确位置。
- 检查
- 根据 Fiber 类型执行特定的 Mutation 操作:
- FunctionComponent:主要处理
useEffectEvent
这种特殊 Hook 的插入副作用的卸载和挂载。 - ClassComponent:如果
Ref
标记存在,解绑旧的ref
。 - HostComponent (DOM 元素):解绑旧
ref
;如果ContentReset
标记存在,重置其文本内容;如果Update
标记存在,调用commitHostUpdate
来更新 DOM 节点的属性。 - HostText (文本节点):如果
Update
标记存在,调用commitHostTextUpdate
来更新文本内容。 - 其他类型 (HostRoot, HostPortal, SuspenseComponent, OffscreenComponent 等):执行各自特定的 DOM 操作或状态更新。
- FunctionComponent:主要处理
- 获取当前 Fiber 信息:包括其类型 (
遍历 Effect List 完成:
- 当所有带有
MutationMask
的 Fiber 都处理完毕后,DOM 操作完成。
- 当所有带有
清理阶段状态:
- 重置之前设置的全局变量。
commitMutationEffects
结束:- Mutation 阶段结束,接下来通常是 Layout Effects 阶段。
关键要点
- 顺序:Mutation 阶段的操作遵循一定的顺序:通常是先处理删除 (自顶向下递归卸载,然后移除 DOM),然后处理子节点的 Mutations,再处理当前节点的插入和更新。
- DOM 操作:这是 React 实际修改浏览器 DOM 的阶段。
- Ref 解绑:旧的 ref 会在这个阶段被解绑。
componentWillUnmount
:类组件的componentWillUnmount
生命周期方法在此阶段被调用(在commitDeletionEffects
内部)。- Hooks 清理:
useEffect
和useLayoutEffect
的清理函数逻辑上属于卸载的一部分,其执行时机虽然分别在 Passive 和 Layout 阶段,但触发点与此处的删除流程相关联。
流程图
核心函数源码解析
在这个阶段,其实质是调用 commitMutationEffectsOnFiber
函数来处理 finishedWork
(已完成的工作树)中的副作用。
commitMutationEffectsOnFiber
javascript
function commitMutationEffectsOnFiber(
finishedWork: Fiber,
root: FiberRoot,
lanes: Lanes,
) {
// ... Profiler 相关代码 ...
const current = finishedWork.alternate
const flags = finishedWork.flags
switch (finishedWork.tag) {
case FunctionComponent:
case ForwardRef:
case MemoComponent:
case SimpleMemoComponent: {
recursivelyTraverseMutationEffects(root, finishedWork, lanes) // 1. 递归处理子节点的 Mutation Effects
commitReconciliationEffects(finishedWork, lanes) // 2. 处理当前节点的 Placement (插入)
if (flags & Update) {
// 3. 如果有 Update 标记 (通常由 Hook 副作用引起)
// 卸载之前的插入副作用钩子 (useEffectEvent)
commitHookEffectListUnmount(
HookInsertion | HookHasEffect,
finishedWork,
finishedWork.return,
)
// 挂载新的插入副作用钩子 (useEffectEvent)
commitHookEffectListMount(
HookInsertion | HookHasEffect,
finishedWork,
)
// 注意:常规的 useEffect 和 useLayoutEffect 的 unmount/mount 在此阶段不执行,
// useLayoutEffect 在 Layout Effects 阶段,useEffect 在 Passive Effects 阶段。
// 此处的 HookInsertion 指的是 useEffectEvent 这种特殊的 Hook。
}
break
}
case ClassComponent: {
recursivelyTraverseMutationEffects(root, finishedWork, lanes) // 1. 递归处理子节点的 Mutation Effects
commitReconciliationEffects(finishedWork, lanes) // 2. 处理当前节点的 Placement (插入)
if (flags & Ref) {
// 3. 如果有 Ref 标记 (解绑旧 ref)
if (!offscreenSubtreeWasHidden && current !== null) {
safelyDetachRef(current, current.return)
}
}
// Callback 标记相关的 deferHiddenCallbacks 逻辑 (与 Offscreen 相关)
break
}
case HostComponent: {
// DOM 元素
recursivelyTraverseMutationEffects(root, finishedWork, lanes) // 1. 递归处理子节点的 Mutation Effects
commitReconciliationEffects(finishedWork, lanes) // 2. 处理当前节点的 Placement (插入)
if (flags & Ref) {
// 3. 如果有 Ref 标记 (解绑旧 ref)
if (!offscreenSubtreeWasHidden && current !== null) {
safelyDetachRef(current, current.return)
}
}
if (supportsMutation) {
if (finishedWork.flags & ContentReset) {
// 4. 如果有 ContentReset 标记 (重置文本内容)
commitHostResetTextContent(finishedWork)
}
if (flags & Update) {
// 5. 如果有 Update 标记 (更新 DOM 属性)
const instance: Instance = finishedWork.stateNode
if (instance != null) {
const newProps = finishedWork.memoizedProps
const oldProps =
current !== null ? current.memoizedProps : newProps
commitHostUpdate(finishedWork, newProps, oldProps) // 调用 commitHostUpdate 执行 DOM 更新
}
}
// FormReset 标记相关逻辑
}
break
}
case HostText: {
// 文本节点
recursivelyTraverseMutationEffects(root, finishedWork, lanes) // 1. 递归处理子节点的 Mutation Effects
commitReconciliationEffects(finishedWork, lanes) // 2. 处理当前节点的 Placement (插入)
if (flags & Update) {
// 3. 如果有 Update 标记 (更新文本内容)
if (supportsMutation) {
// ... 省略错误检查 ...
const newText: string = finishedWork.memoizedProps
const oldText: string =
current !== null ? current.memoizedProps : newText
commitHostTextUpdate(finishedWork, newText, oldText) // 调用 commitHostTextUpdate 更新文本
}
}
break
}
case HostRoot: {
// ... Profiler 和 Hoistable 相关代码 ...
recursivelyTraverseMutationEffects(root, finishedWork, lanes) // 1. 递归处理子节点的 Mutation Effects
commitReconciliationEffects(finishedWork, lanes) // 2. 处理当前节点的 Placement (插入)
if (flags & Update) {
// 处理 Dehydrated 容器的提交
// ...
// 处理持久化模式下的根容器子节点
// ...
}
// FormReset 相关逻辑
// ...
break
}
case HostPortal: {
// ... Hoistable 相关代码 ...
recursivelyTraverseMutationEffects(root, finishedWork, lanes) // 1. 递归处理子节点的 Mutation Effects
commitReconciliationEffects(finishedWork, lanes) // 2. 处理当前节点的 Placement (插入)
// ... 更新持久化模式下的 Portal 子节点 ...
break
}
case SuspenseComponent: {
recursivelyTraverseMutationEffects(root, finishedWork, lanes)
commitReconciliationEffects(finishedWork, lanes)
// ... 处理 Suspense 的回调和重试队列 ...
break
}
case OffscreenComponent: {
// ... 根据 Offscreen 状态处理子节点的 Mutation Effects 和 Reconciliation Effects ...
// ... 处理 Visibility 变化,可能隐藏或显示子树 ...
// ... 处理 Offscreen 的重试队列 ...
break
}
// ... 其他 Fiber类型的处理,如 ViewTransitionComponent, ScopeComponent 等 ...
default: {
recursivelyTraverseMutationEffects(root, finishedWork, lanes)
commitReconciliationEffects(finishedWork, lanes)
break
}
}
// ... Profiler 相关代码 ...
}
其核心流程可以总结为:
- 向下递归阶段 (Pre-order traversal like):
- 调用
recursivelyTraverseMutationEffects(root, finishedWork, lanes)
:这个函数会先处理当前 Fiber 节点的deletions
列表(执行删除),然后再递归地对子 Fiber 调用commitMutationEffectsOnFiber
。
- 调用
- 当前节点处理阶段:
- 调用
commitReconciliationEffects(finishedWork, lanes)
:如果当前 Fiber 有Placement
标记,则执行 DOM 插入操作(通过commitHostPlacement
)。 - 根据 Fiber 的
tag
和flags
执行特定的 DOM 操作:- FunctionComponent/ForwardRef/MemoComponent/SimpleMemoComponent: 主要处理
useEffectEvent
相关的卸载和挂载。 - ClassComponent: 解绑旧的
ref
(如果Ref
flag 存在且组件未隐藏)。 - HostComponent: 解绑旧
ref
,重置文本内容 (ContentReset
),更新 DOM 属性 (Update
flag,调用commitHostUpdate
)。 - HostText: 更新文本内容 (
Update
flag,调用commitHostTextUpdate
)。 - HostRoot/HostPortal: 特殊处理,例如处理脱水、持久化等。
- SuspenseComponent/OffscreenComponent: 处理可见性变化、回调、重试队列等。
- FunctionComponent/ForwardRef/MemoComponent/SimpleMemoComponent: 主要处理
- 调用
