提交阶段真正执行 DOM 变更、生命周期、副作用 的核心入口,不可中断同步 Commit 阶段的总调度器
一、完整调用路径
scss
commitRoot 入口
│
├─ markRootFinished(...) ← 更新 root.pendingLanes
│
├─ 调度 passive effects 回调 ← NormalSchedulerPriority
│
├─ commitBeforeMutationEffects ← getSnapshotBeforeUpdate
│ executionContext |= CommitContext
│
├─ 切换 ViewTransition 分支?
│ ├─ Yes: startViewTransition() ← 浏览器接管过渡动画
│ │ ├─ flushMutationEffects
│ │ ├─ flushAfterMutationEffects
│ │ ├─ flushLayoutEffects
│ │ └─ flushSpawnedWork
│ └─ No:
│ ├─ flushMutationEffects ← DOM 突变
│ │ ├─ commitMutationEffects
│ │ ├─ resetAfterCommit
│ │ └─ root.current = finishedWork ← 切换双缓冲
│ │
│ ├─ flushLayoutEffects ← componentDidMount/Update
│ │ ├─ commitLayoutEffects
│ │ └─ (useLayoutEffect 同步执行)
│ │
│ └─ flushSpawnedWork ← 后处理
│ ├─ requestPaint()
│ ├─ 检查 passive effects
│ ├─ 调度或释放 cache
│ └─ onCommitRootDevTools
└─ (稍后) flushPassiveEffects ──── useEffect 回调
二、源码解析
步骤一、计算本次渲染占用总车道,合并并发更新车道
javascript
// 待保留车道集合
let remainingLanes = mergeLanes(finishedWork.lanes, finishedWork.childLanes);
pendingEffectsRemainingLanes = remainingLanes;
// 获取渲染阶段并发事件新增的更新车道
const concurrentlyUpdatedLanes = getConcurrentlyUpdatedLanes();
remainingLanes = mergeLanes(remainingLanes, concurrentlyUpdatedLanes);
作用
- 合并根
Fiber自身车道 + 整棵子树聚合车道,得到本次渲染覆盖的全部车道,提交完后,这些车道可以标记为已完成 ,从root.pendingLanes清除 - 存入全局
pendingEffectsRemainingLanes,供副作用日志、调度器读取 - 获取渲染阶段并发事件新增的更新车道
- 合并并发更新车道,不把并发新增更新标记为已完成
设计意义
markRootFinished会从root.pendingLanes中减去本次提交的lanes,但保留上述剩余 lanes ,即本次提交没有覆盖到的更新。交插更新尤其关键:并发渲染过程中用户可触发新setState,不能直接清空根的pendingLanes;通过合并并发车道保留未完成更新,避免更新丢失、调度饥饿
步骤二、手势车道清理
javascript
if (enableGestureTransition && root.pendingGestures === null) {
if (enableProfilerTimer && (remainingLanes & GestureLane) !== NoLanes) {
clearGestureUpdates();
}
remainingLanes &= ~GestureLane;
}
作用
当没有待处理的手势时,从剩余 lanes 中移除 GestureLane,防止没有手势的情况下持续调度手势 lane 的渲染。
设计意义:
GestureLane 是一种特殊的 lane,在用户触摸/滑动手势期间被设置。手势结束后,pendingGestures 变为 null,此时 GestureLane 不再需要被调度,清除它避免无意义的渲染循环
步骤三、根车道状态收尾(核心调度状态更新)
渲染提交完成后的根状态清理 / 重置核心函数,一轮渲染 + DOM 提交全部完成后,统一更新 FiberRoot 上所有车道相关调度状态
javascript
markRootFinished(
root,
lanes, // 本次提交的 lane
remainingLanes, // 保留的 lane(未处理 + 交插更新)
spawnedLane, // 本次渲染中 spawned 的 lane
updatedLanes, // 交插更新 lane
suspendedRetryLanes, // 挂起重试 lane
);
function markRootFinished(
root,
finishedLanes,
remainingLanes,
spawnedLane,
updatedLanes,
suspendedRetryLanes,
) {
// 提交前根上所有排队待执行车道
const previouslyPendingLanes = root.pendingLanes;
// 旧排队车道 减去 要保留的车道 = 本轮彻底处理完毕、可以全部清理销毁的车道集合
const noLongerPendingLanes = previouslyPendingLanes & ~remainingLanes;
// 重置根核心待调度车道,只保留渲染中途并发新增更新、本轮未处理完的延迟派生车道
root.pendingLanes = remainingLanes;
// 临时标记为挂起等待数据的车道
root.suspendedLanes = NoLanes;
// 被高优更新唤醒、需要重新调度的车道
root.pingedLanes = NoLanes;
// 预热预渲染车道(预加载 Suspense 资源后台渲染)
root.warmLanes = NoLanes;
if (enableDefaultTransitionIndicator) {
// 过渡指示器车道过滤保留
root.indicatorLanes &= remainingLanes;
}
// 清空车道纠缠标记
root.entangledLanes = NoLanes;
// 重置错误恢复过滤
root.errorRecoveryDisabledLanes &= remainingLanes;
// 重置根外壳挂起计数器
root.shellSuspendCounter = 0;
// 每条车道绑定的纠缠关联车道
const entanglements = root.entanglements;
// 每条车道的过期时间戳
const expirationTimes = root.expirationTimes;
// 离屏容器(Offscreen)内缓存的更新队列
const hiddenUpdates = root.hiddenUpdates;
// 循环遍历所有已完成车道,批量销毁配套缓存
let lanes = noLongerPendingLanes;
while (lanes > 0) {
// 从位掩码中取出任意一条车道对应的数组下标
const index = pickArbitraryLaneIndex(lanes);
const lane = 1 << index;
// 清空该车道的纠缠绑定、过期时间戳
entanglements[index] = NoLanes;
expirationTimes[index] = NoTimestamp;
// 处理离屏隐藏更新
const hiddenUpdatesForLane = hiddenUpdates[index];
if (hiddenUpdatesForLane !== null) {
// 清空数组引用,释放 GC 内存
hiddenUpdates[index] = null;
// 遍历内部所有更新,移除 Offscreen 离屏标记,若组件重新挂载可正常参与渲染
for (let i = 0; i < hiddenUpdatesForLane.length; i++) {
const update = hiddenUpdatesForLane[i];
if (update !== null) {
update.lane &= ~OffscreenLane;
}
}
}
// 从待清理集合中移除当前车道,继续循环直到全部清空
lanes &= ~lane;
}
// 处理渲染过程派生的延迟车道 spawnedLane
if (spawnedLane !== NoLane) {
// 将派生车道注册到根延迟调度队列,标记为低优延后执行
markSpawnedDeferredLane(
root,
spawnedLane,
NoLanes,
);
}
// 存在 Suspense 重试车道且本轮渲染无实际 DOM 变更且非旧版同步根节点
if (
suspendedRetryLanes !== NoLanes &&
updatedLanes === NoLanes &&
!(disableLegacyMode && root.tag === LegacyRoot)
) {
// 筛选出本轮全新生成、之前未存在的重试车道
const freshlySpawnedRetryLanes =
suspendedRetryLanes &
~(previouslyPendingLanes & ~finishedLanes);
// 按位或追加到 root.suspendedLanes,标记这些车道处于数据挂起等待状态
root.suspendedLanes |= freshlySpawnedRetryLanes;
}
}
作用 :更新 root 的 lane 追踪数据结构
- 将本次已提交的
lanes从root.pendingLanes中清除,只保留remainingLanes(含并发新增更新、延迟派生车道),通过位运算拆分已完成待清理车道 与待保留车道 - 把未完成的
remainingLanes留在根待调度队列,仅保留未处理并发更新、延迟车道,下一轮ensureRootIsScheduled会自动调度 - 清空本轮已处理完毕车道对应的辅助缓存:全局挂起、
ping唤醒、预热车道、过渡指示器、错误恢复、顶层挂起计数器 - 循环清理所有已完成车道的配套缓存:解绑纠缠、清空过期时间、释放离屏更新内存、清除
Offscreen标记 - 处理渲染派生延迟车道
spawnedLane(渲染调和过程中新生成的延迟任务车道-Suspense恢复、Transition衍生更新)、将派生车道注册到根延迟调度队列,标记为低优延后执行 - 处理本轮新产生的
Suspense重试挂起车道suspendedRetryLanes,筛选出本轮全新生成、之前未存在的重试车道,按位或追加到root.suspendedLanes,标记这些车道处于数据挂起等待状态。仅纯fallback加载渲染才持久化Suspense重试挂起车道,真实数据渲染完成无需保留重试标记
设计意义
- 调度器依靠该函数区分 "已完成更新" 和 "待执行更新",是并发调度饥饿防护、更新排队的底层支撑
- 基于位掩码的车道集合管理:全部车道集合操作使用按位与 / 或 / 取反,集合拆分、过滤、合并均为 O (1) 运算;仅在清理缓存时按需循环已完成车道,无无效遍历,适配高频并发更新场景
- 分层分离两类车道生命周期
- 已完成车道(
noLongerPendingLanes):配套缓存全部销毁、内存释放、标记清零 - 待保留车道(
remainingLanes):完整保留在pendingLanes,包含渲染中途并发新增用户更新,彻底解决并发更新丢失问题
- 已完成车道(
步骤四、重置提交阶段更新标记
javascript
didIncludeCommitPhaseUpdate = false;
作用
标记提交阶段是否产生嵌套 setState,重置为初始 false;在 mutation/layout 阶段(如 componentDidMount 中调用 setState)若触发更新会置为 true,用于后续重新调度渲染
设计意义
检测提交阶段副作用内嵌套更新,保证提交完成后自动发起新一轮渲染,不丢失同步副作用更新
步骤五、视图过渡:区分被动副作用掩码
javascript
let passiveSubtreeMask;
if (enableViewTransition) {
if (includesOnlyViewTransitionEligibleLanes(lanes)) {
// 清空过渡事件缓存
pendingTransitionTypes = claimQueuedTransitionTypes(root);
// 包含 Transition 相关 flags
passiveSubtreeMask = PassiveTransitionMask;
} else {
// 标准 passive flags
passiveSubtreeMask = PassiveMask;
}
} else {
passiveSubtreeMask = PassiveMask;
}
作用
- 开启视图过渡时清空过渡事件缓存
- 纯视图过渡更新:抢占排队过渡类型,使用
PassiveTransitionMask掩码(专属过渡副作用) - 普通更新使用基础
PassiveMask(常规useEffect)
设计意义
ViewTransition 过渡动画有专属被动副作用逻辑,通过掩码区分普通 effect 和过渡 effect,互不干扰,适配浏览器原生页面快照动画
步骤六、判断是否存在被动副作用,调度异步 useEffect 任务
javascript
// 子树含被动标记或根自身含被动标记
if (
(finishedWork.subtreeFlags & passiveSubtreeMask) !== NoFlags ||
(finishedWork.flags & passiveSubtreeMask) !== NoFlags
) {
if (enableYieldingBeforePassive) {
} else {
// 有 passive effects → 调度回调
root.callbackNode = null;
root.callbackPriority = NoLane;
scheduleCallback(NormalSchedulerPriority, () => {
// 标记本次提交属于延迟被动副作用提交
if (pendingDelayedCommitReason === IMMEDIATE_COMMIT) {
pendingDelayedCommitReason = DELAYED_PASSIVE_COMMIT;
}
flushPassiveEffects();
return null;
});
}
} else {
// 无 passive effects → 清理调度
root.callbackNode = null;
root.callbackPriority = NoLane;
}
作用
- 旧版调度:先检查
fiber树中是否有Passive flag(即useEffect回调),如果有,立即向Scheduler注册普通优先级异步回调NormalSchedulerPriority,延迟执行flushPassiveEffects(所有useEffect统一在此执行)。被动副作用与页面渲染更新是两条完全独立的Scheduler任务队列,重复渲染会产生大量零散独立回调,增加调度器任务队列压力 - 新版让步调度
enableYieldingBeforePassive:移除独立回调,复用根调度逻辑,渲染主任务(render + commit)和passive副作用合并到同一个Scheduler任务 - 无副作用时清空根上缓存的回调句柄,释放内存
设计意义
- 任务合并优化 :把剩余渲染更新 和被动副作用批量执行 合并到同一个调度回调,减少
Scheduler队列任务碎片 - 状态统一管控 :全部调度逻辑收敛到
ensureRootIsScheduled,根的callbackNode、callbackPriority统一管理,避免多处重复创建 / 清除回调
步骤七、重置视图过渡启动标记
javascript
resetShouldStartViewTransition();
作用
清空全局视图过渡启动标记,避免上一轮过渡状态污染本次提交;后续 DOM 操作完成后重新判定是否开启新过渡动画。
设计意义
视图过渡状态全局单例,每次提交前置重置,保证每轮更新独立判断动画条件
步骤八、Before Mutation 阶段(快照阶段)
javascript
const subtreeHasBeforeMutationEffects =
(finishedWork.subtreeFlags & (BeforeMutationMask | MutationMask)) !== NoFlags;
const rootHasBeforeMutationEffect =
(finishedWork.flags & (BeforeMutationMask | MutationMask)) !== NoFlags;
if (subtreeHasBeforeMutationEffects || rootHasBeforeMutationEffect) {
const prevTransition = ReactSharedInternals.T;
ReactSharedInternals.T = null; // 清除 Transition 上下文
const previousPriority = getCurrentUpdatePriority();
setCurrentUpdatePriority(DiscreteEventPriority); // 设置为离散事件优先级
const prevExecutionContext = executionContext;
executionContext |= CommitContext; // 设置 CommitContext
try {
commitBeforeMutationEffects(root, finishedWork, lanes);
} finally {
executionContext = prevExecutionContext;
setCurrentUpdatePriority(previousPriority);
ReactSharedInternals.T = prevTransition;
}
}
作用
- 通过根
Fiber预聚合subtreeFlags位标记,O (1) 判断整棵树是否存在快照、DOM修改副作用,无需递归遍历全树 - 清除
Transition上下文,getSnapshotBeforeUpdate不应感知Transition - 切换为离散事件最高优先级、标记进入
Commit上下文 - 执行
commitBeforeMutationEffects:遍历effect链表执行getSnapshotBeforeUpdate,读取变更前DOM布局、尺寸、滚动位置; - 完整恢复全部全局现场,防止上下文污染后续逻辑
设计意义
快照必须在 DOM 修改前同步读取,避免布局抖动;使用 try/finally 保证全局上下文、优先级、Transition 状态一定会恢复,防止状态泄漏
步骤九、手势提交收尾
javascript
if (enableGestureTransition) {
if (isGestureRender(lanes)) {
stopCommittedGesture(root);
}
}
作用
本次渲染属于手势交互更新,DOM 提交完成后终止手势动画状态,清空根 pendingGestures 标记。
设计意义
手势生命周期与提交阶段绑定,DOM 变更落地后结束交互动画,避免残留手势阻塞后续更新
步骤十、进入 DOM变更阶段
javascript
// 标记进入 DOM 变更阶段
pendingEffectsStatus = PENDING_MUTATION_PHASE;
if (enableViewTransition && shouldStartViewTransition) {
// ── ViewTransition 异步路径 ──
if (enableProfilerTimer && enableComponentPerformanceTrack) {
startAnimating(lanes);
}
pendingViewTransition = startViewTransition(
suspendedState,
root.containerInfo,
pendingTransitionTypes,
flushMutationEffects, // ← stage 1: DOM 突变
flushLayoutEffects, // ← stage 2: layout effects
flushAfterMutationEffects, // ← stage 3: after mutation effects
flushSpawnedWork, // ← stage 4: spawned work
flushPassiveEffects, // ← stage 5: passive effects
reportViewTransitionError,
enableProfilerTimer ? suspendedViewTransition : null,
enableProfilerTimer ? finishedViewTransition.bind(null, lanes) : null,
);
} else {
// ── 同步路径 ──
flushMutationEffects(); // ① DOM 突变
flushLayoutEffects(); // ② Layout effects
flushSpawnedWork(); // ③ 后处理
}
作用
- 全局标记当前副作用阶段为
DOM变更阶段 - 开启视图过渡
- 启动浏览器原生
document.startViewTransition - 将三段同步副作用(
mutation/layout/afterMutation)、派生更新、被动effect全部作为回调传给过渡API - 浏览器会先捕获页面快照,再执行
DOM修改,最后播放过渡动画,解决页面切换撕裂问题
- 启动浏览器原生
- 普通更新
flushMutationEffects:第二阶段,真实DOM增删改、卸载组件、更新属性、删除旧 DOMflushLayoutEffects:第三阶段,同步布局副作用useLayoutEffect、componentDidMount/componentDidUpdate、ref赋值flushSpawnedWork:执行渲染 / 提交过程中派生的延迟更新、Suspense恢复任务
设计意义
- 对接原生
Web API,把同步DOM变更逻辑封装在浏览器动画生命周期内,兼顾React副作用体系与原生过渡动画能力 - 普通更新同步串行执行三段提交,严格遵守执行顺序:快照 →
DOM修改 → 同步布局副作用,保证DOM操作与生命周期执行顺序稳定可预测
三、设计思想
- 三阶段严格分离 :
Before Mutation(读DOM)→Mutation(写DOM)→Layout(读DOM+ 同步effect) - 独立分流视图过渡、手势交互两套特殊提交流程 :ViewTransition 异步提交,通过
startViewTransition将DOM突变和layout推迟到浏览器过渡动画帧中执行,不影响动画流畅度, - 并发调度闭环设计 :前置通过
mergeLanes+markRootFinished统一管理根待更新车道,区分渲染阶段新增并发更新,不会误清空排队任务;提交完成后自动暴露剩余车道给上层ensureRootIsScheduled,形成更新调度→渲染调和→提交收尾→重新调度完整闭环,解决并发任务饥饿问题 DiscreteEventPriority设置 :commit三阶段中优先级临时提升为DiscreteEventPriority,确保同步不可中断CommitContext保护 :三阶段中设置executionContext |= CommitContext,阻止commit期间的setState触发新渲染