completeRoot 源码解析

渲染调和结束后、正式进入 DOM 提交前的统一预处理入口,清理 pending 状态、验证安全性、设置全局效果变量、特殊处理手势过渡(gesture transition

一、完整调用链

kotlin 复制代码
completeRoot(root, finishedWork, lanes, recoverableErrors, transitions, didIncludeRenderPhaseUpdate, spawnedLane, updatedLanes, suspendedRetryLanes, didSkipSuspendedSiblings, exitStatus, suspendedState)
  │
  ├─ ① root.cancelPendingCommit = null
  ├─ ② flushPendingEffects() (do-while)
  ├─ ③ 安全断言
  ├─ ④ finishedWork === null? → return
  ├─ ⑤ 防同一树检查
  ├─ ⑥ 清除 workInProgressRoot
  ├─ ⑦ 设 pending 全局变量
  ├─ ⑧ Gesture 特殊处理? → 延迟提交 + return   
  └─ ⑨ commitRoot(root, finishedWork, lanes, spawnedLane, updatedLanes, suspendedRetryLanes, suspendedState)

二、源码解析

参数解析

字段名 含义说明
root FiberRoot 根容器
finishedWork 调和完成的 workInProgressFibernull 代表无 DOM 更新
lanes 本次渲染优先级车道
recoverableErrors 调和捕获的可恢复错误集合(ErrorBoundary 降级用)
transitions 本次关联的 useTransition 任务
didIncludeRenderPhaseUpdate 渲染阶段是否产生嵌套 setState 更新
spawnedLane 渲染派生的延迟车道
updatedLanes 本次变更涉及车道
suspendedRetryLanes Suspense 重试车道
didSkipSuspendedSiblings 渲染是否跳过挂起子节点
exitStatus 渲染结束状态 RootCompleted/RootErrored/RootSuspended
suspendedState 提交挂起上下文(视图过渡 / 媒体等待)

步骤一、清空根容器待取消异步提交句柄

javascript 复制代码
root.cancelPendingCommit = null;

作用

清除之前 completeRootWhenReady 中设置的可能待执行的 commit 回调(资源加载完成后调用),防止旧回调篡改本次提交

设计意义

当前 completeRoot 正在提交,即资源已完成或无需等待。任何之前注册的待执行 cancelPendingCommit 回调都必须清除,否则会在当前提交后额外触发一次 completeRoot,导致重复提交,统一根状态清理,避免多轮延时提交互相干扰,防止内存泄漏、无效回调执行


步骤二、循环刷新所有 pending 被动副作用

javascript 复制代码
do {
  flushPendingEffects();
} while (pendingEffectsStatus !== NO_PENDING_EFFECTS);

作用

在开始新的 commit 之前,强制刷新任何尚未处理的 passive effects(如 useEffect 回调),执行排队的 useEffect/useLayoutEffect 衍生更新,保证进入提交阶段前渲染阶段所有嵌套更新全部执行完毕

设计意义

嵌套 commit 安全flushPendingEffects 内部会调用 flushPassiveEffects,而 flushPassiveEffects 末尾可能调用 flushSyncUpdateQueue,产生新的同步渲染和 commitdo-while 循环确保所有的副作用链都被消费完毕后才开始当前 commit,避免前一个 commit 的被动 effect 在当前 commitDOM 上操作。解决 effect 内部 setState 产生嵌套更新 的闭环问题,本轮 CommitDOM 变更在执行 Effect 之前已经固化,Effect 中触发的状态更新只会进入下一轮渲染流程,在下一次 Commit 才更新真实 DOM


步骤三、禁止嵌套在 Render/Commit 上下文执行提交

javascript 复制代码
if ((executionContext & (RenderContext | CommitContext)) !== NoContext) {
  throw new Error('Should not already be working.');
}

作用

确保当前不在渲染(RenderContext)或提交(CommitContext)中,completeRoot 不应被递归调用,禁止嵌套在 Render/Commit 上下文执行提交

设计意义

这是 React上下文守卫executionContext 是一个按位掩码,跟踪当前正在进行的操作。如果已经到了 completeRoot,理论上前面 performWorkOnRoot 中设置的 RenderContext 已被清除。如果仍存在,说明有递归 bug(如提交过程中触发 setState 又导致提交)


步骤四、调和完成树为空保护

javascript 复制代码
if (finishedWork === null) {
  if (enableSchedulingProfiler) { markCommitStopped(); }
  if (enableGestureTransition) {
    if (isGestureRender(lanes)) { stopCommittedGesture(root); }
  }
  return;
}

作用

finishedWorknull 时跳过整个 commit,不提交任何变更,手势渲染场景终止手势动画

设计意义

这是一个防御性检查。正常情况下 finishedWork 不应为 null,但在某些退化路径(如前述 gesture 特殊处理)中可能调用 completeRoot 返回


步骤五、禁止提交 current 旧树

javascript 复制代码
if (finishedWork === root.current) {
  throw new Error('Cannot commit the same tree as before.');
}

设计意义

root.current 指向当前屏幕上显示的 fiber 树。finishedWork 是刚刚渲染完成的 WiP 树。如果两者是同一个 fiber 对象,说明没有任何变更------重复提交是无意义的,且可能在 mutation 阶段销毁 DOM 节点


步骤六、清空全局 workInProgress 全局缓存(匹配根才重置)

javascript 复制代码
if (root === workInProgressRoot) {
  workInProgressRoot = null;
  workInProgress = null;
  workInProgressRootRenderLanes = NoLanes;
}

作用

如果当前提交的 rootworkInProgressRoot 一致,清除所有 WiP 全局变量。延时提交场景(setTimeout / 视图过渡阻塞)会出现渲染根与提交根不一致,保留全局缓存,不强制清空

设计意义

双缓冲交替 :渲染完成后,workInProgressRoot 不再有意义。清除后,下次 ensureRootIsScheduled 会从 root.pendingLanes 重新开始,prepareFreshStack 会创建全新的 WiP


步骤七、缓存本次渲染全量上下文至全局 pending 变量

javascript 复制代码
pendingFinishedWork = finishedWork;
pendingEffectsRoot = root;
pendingEffectsLanes = lanes;
pendingPassiveTransitions = transitions;
pendingRecoverableErrors = recoverableErrors;
pendingDidIncludeRenderPhaseUpdate = didIncludeRenderPhaseUpdate;

作用

将此次 commit 的所有数据存储到全局 pending 变量中,供后续的 flushMutationEffectsflushLayoutEffectsflushPassiveEffects 等阶段函数读取。

设计意义

  • 全局状态解耦completeRootcommitRoot 之间通过全局变量传递数据,而不是深层嵌套的函数参数。这样 commitRoot 中的各个 phase 函数(flushMutationEffectsflushLayoutEffects)可以在任何时候被调用(如 ViewTransition 的异步回调中),无需重新传递所有参数
  • 解决异步延时提交上下文丢失问题 。隔离渲染临时变量与提交上下文,保证延时回调能拿到完整渲染信息。延时提交(setTimeout、视图过渡阻塞)过程中,全局 workInProgress 变量会被新渲染覆盖,因此必须提前把本次提交所需全部上下文存入独立 pending 全局变量,供后续 commitRoot 使用

步骤八、手势动画分流逻辑(核心延时分支)

javascript 复制代码
if (enableGestureTransition && isGestureRender(lanes)) {
  const committingGesture = root.pendingGestures;
  if (committingGesture !== null && !committingGesture.committing) {
    const didAttemptEntireTree = !didSkipSuspendedSiblings;
    // 手势未就绪,标记根挂起,注册延时手势提交回调
    markRootSuspended(root, lanes, spawnedLane, didAttemptEntireTree);
    if (committingGesture.running === null) {
      applyGestureOnRoot(
        root,
        finishedWork,
        recoverableErrors,
        suspendedState,
        enableProfilerTimer
          ? suspendedCommitReason === null
            ? completedRenderEndTime
            : commitStartTime
          : 0,
      );            // 启动手势动画
    } else {
      // 已有正在运行手势,放弃本次提交,清空pending上下文用于GC
      pendingEffectsRoot = null;         
      pendingFinishedWork = null;
      pendingEffectsLanes = NoLanes;
    }
    // 注册手势完成后执行完整completeRoot回调
    root.cancelPendingCommit = scheduleGestureCommit(
      committingGesture,
      completeRoot.bind(
        null,
        root,
        finishedWork,
        lanes,
        recoverableErrors,
        transitions,
        didIncludeRenderPhaseUpdate,
        spawnedLane,
        updatedLanes,
        suspendedRetryLanes,
        didSkipSuspendedSiblings,
        exitStatus,
        suspendedState,
        completedRenderStartTime,
        completedRenderEndTime,
      )
    );
    return;                               // ← 不调用 commitRoot
  }
}

作用 :当当前渲染是手势过渡(如手势驱动的动画)时,不立即提交。等待手势动画完成后(如 requestAnimationFrame)再提交

  • 识别手势渲染车道,读取根上待执行手势任务
  • 手势存在且未进入提交状态
    • 标记根车道挂起
    • 无运行中手势:绑定新手势动画上下文到根,启动手势动画
    • 已有运行手势:清空本次所有 pending 提交上下文,回收内存,等待当前手势完成后再提交
  • 绑定完整 completeRoot 闭包回调,注册到手势完成调度器
  • return 终止函数,不进入 commitRoot,等待手势动画结束再提交 DOM

设计意义

手势驱动的更新(如拖拽、滑动)不需要立即提交 DOM,先让手势动画流畅运行,手势结束后再同步最终状态。原生手势动画与 React 渲染调度协同,避免动画中途 DOM 变更造成抖动;支持手势期间阻塞提交,动画完成后自动执行更新,提供流畅交互体验


步骤九、无阻塞手势 / 过渡,进入真实 DOM 提交

javascript 复制代码
commitRoot(
  root, finishedWork, lanes, spawnedLane,
  updatedLanes, suspendedRetryLanes, suspendedState,
  suspendedCommitReason, completedRenderEndTime,
);

作用

所有前置校验、副作用刷新、分流逻辑执行完毕,调用底层 commitRoot,同步执行三阶段提交


三、设计思想

  • 全局 pending 变量commit 相关数据通过 pendingFinishedWork/pendingEffectsRoot 等全局变量传递,支持异步回调中提交(如 ViewTransition
  • 副作用闭环收敛commitdo-while 刷新所有 pending passive effects,防止嵌套 commit 的时序问题
  • Gesture 退让:手势驱动的更新延迟提交,保证手势动画 60fps 不卡顿
  • 防止重复提交cancelPendingCommit 清除 + finishedWork === root.current 检查,双重保障
相关推荐
光影少年18 小时前
React 合成事件机制、和原生事件区别、事件冒泡阻止
前端·react.js·掘金·金石计划
YFF菲菲兔1 天前
finishConcurrentRender 源码解析
react.js
YFF菲菲兔1 天前
reconcileChildren 源码解析
react.js
还有多久拿退休金2 天前
Ant Design Tree 搜索定位避坑指南:虚拟滚动下如何实现高亮与精准定位
前端·react.js
光影少年2 天前
react 原理与进阶
前端·react.js·掘金·金石计划
饼饼饼2 天前
React19 状态解惑:State 没那么神秘,一文读懂 React 状态不可变原则与 Hooks 底层链表
前端·react.js
花椒技术2 天前
RN 多包热更新实践:更新校验、运行时加载与 Bridge 缓存治理
react native·react.js·harmonyos
互联网推荐官2 天前
上海 APP 开发服务甄选:技术架构设计、全维度判断框架
javascript·react native·react.js·app开发·开发经验·上海