Skip to content

DOM Mutations 阶段详解

在 DOM Mutations 阶段,React 将会把在 Render Phase 计算出来的所有变更(新增、更新、删除)真实地应用到宿主环境的 DOM 树上。这个阶段的操作是同步执行的,并且会阻塞浏览器的渲染,直到所有变更完成。

内部流程

  1. commitRoot 调用 commitMutationEffects

    • 这是 Mutation 阶段的入口。
  2. 初始化阶段状态

    • 设置一些全局变量,如 inProgressLanesinProgressRoot,用于跟踪当前的提交上下文。
  3. 调用 commitMutationEffectsOnFiber 开始处理 Effect List

    • 这是实际执行 DOM 操作的核心循环的起点。它会从 finishedWork (根 Fiber 或发生更新的子树的根) 开始,并递归地处理 effect list 中的 Fiber 节点。
  4. 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 节点从其父节点中移除。
      • 递归调用 commitMutationEffectsOnFiber 处理子 Fiber:完成删除后,继续向下递归处理当前 Fiber 的子节点中带有 MutationMask 的 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 操作或状态更新。
  5. 遍历 Effect List 完成

    • 当所有带有 MutationMask 的 Fiber 都处理完毕后,DOM 操作完成。
  6. 清理阶段状态

    • 重置之前设置的全局变量。
  7. commitMutationEffects 结束

    • Mutation 阶段结束,接下来通常是 Layout Effects 阶段。

关键要点

  • 顺序:Mutation 阶段的操作遵循一定的顺序:通常是先处理删除 (自顶向下递归卸载,然后移除 DOM),然后处理子节点的 Mutations,再处理当前节点的插入和更新。
  • DOM 操作:这是 React 实际修改浏览器 DOM 的阶段。
  • Ref 解绑:旧的 ref 会在这个阶段被解绑。
  • componentWillUnmount:类组件的 componentWillUnmount 生命周期方法在此阶段被调用(在 commitDeletionEffects 内部)。
  • Hooks 清理useEffectuseLayoutEffect 的清理函数逻辑上属于卸载的一部分,其执行时机虽然分别在 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):
    1. 调用 recursivelyTraverseMutationEffects(root, finishedWork, lanes):这个函数会先处理当前 Fiber 节点的 deletions 列表(执行删除),然后再递归地对子 Fiber 调用 commitMutationEffectsOnFiber
  • 当前节点处理阶段:
    1. 调用 commitReconciliationEffects(finishedWork, lanes):如果当前 Fiber 有 Placement 标记,则执行 DOM 插入操作(通过 commitHostPlacement)。
    2. 根据 Fiber 的 tagflags 执行特定的 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: 处理可见性变化、回调、重试队列等。

微信公众号二维码