上一篇我们已经讲到:root.render(<App />) 并不会直接把 App 渲染成 DOM,而是把 App 包装成一次更新,塞进 HostRootFiber.updateQueue 里,然后进入 scheduleUpdateOnFiber。
这一篇继续往下走,重点讲清楚一个问题:
update 已经挂到 Fiber 上了,React 接下来是怎么把这次更新交给调度系统的?
这也是 React 18 和 React 19 差异比较明显的地方。React 18 中,ensureRootIsScheduled 本身承担了比较重的调度判断;而 React 19 中,这部分被拆成了 Root 级别的微任务调度模型:先把 Root 加入全局调度链表,再安排一个微任务,真正的优先级判断和任务注册延后到微任务中处理。React main 分支源码里,ensureRootIsScheduled 的注释也明确写了它主要做两件事:保证 Root 在调度链表中,以及保证有一个 pending microtask 处理 Root schedule。
一、先回到 scheduleUpdateOnFiber
不管是初次渲染,还是后续 setState、dispatch、forceUpdate,最终都会进入类似这样的链路:
scheduleUpdateOnFiber(root, fiber, lane)
这里有三个核心参数:
root
代表整个 React 应用的根,也就是 FiberRootNode。
fiber
代表这次更新来源对应的 Fiber。
初次渲染时,一般就是 HostRootFiber。
后续组件更新时,可能是某个函数组件 Fiber、类组件 Fiber 等。
lane
代表这次更新的优先级。
比如同步更新是 SyncLane,默认事件更新可能是 DefaultLane,transition 更新会进入 transition 相关 lane。
所以 scheduleUpdateOnFiber 不是"开始渲染"的函数,而是"把某个 Fiber 上发生的更新标记到 Root 上,并推动 Root 进入调度系统"。
它干的核心事情可以压缩成三步:
markRootUpdated(root, lane)
ensureRootIsScheduled(root)
当然源码中还有很多开发环境警告、嵌套更新检查、render phase update 判断、passive effect 标记等逻辑,但主线就是这两个动作。
第一个动作是记录更新。
第二个动作是调度 Root。
也就是说,scheduleUpdateOnFiber 本身还没有进入 beginWork,也没有开始构建 workInProgress tree。它只是告诉 React:
这个 Root 上有一个 lane 级别的更新,需要被安排处理。
二、markRootUpdated 做了什么
markRootUpdated(root, lane) 的核心目的,是把这次更新的 lane 合并到 Root 的 pending 状态里。
你可以把 FiberRootNode 理解成整个应用的调度中心。虽然更新可能发生在某个具体组件 Fiber 上,但 React 真正调度的时候,不是直接调度某个组件,而是调度整个 Root。
原因很简单:React 的渲染不是只算一个组件,而是从 Root 开始,根据 lanes、props、state、context、updateQueue 等信息,决定哪些子树需要重新计算,哪些子树可以 bailout。
所以更新发生在组件上,但调度单位是 Root。
大概可以这样理解:
function markRootUpdated(root, lane) {
root.pendingLanes |= lane
}
真实源码当然更复杂,还涉及 suspended lanes、pinged lanes、expired lanes、event times 等信息。
但是从主线看,你先记住一句话:
markRootUpdated 是把这次更新登记到 Root 上,让 Root 知道自己有活要干。
如果没有这一步,后面的 getNextLanes(root, ...) 就不知道这个 Root 到底还有哪些更新要处理。
三、为什么不是直接开始 render
很多人读源码会卡在这里:
既然 update 已经创建好了,也知道 Root 有 pendingLanes 了,那为什么不直接执行 render?
原因是 React 要先解决三个问题。
第一个问题:这次更新优先级高不高?
用户点击、输入、滚动、普通异步请求、transition,它们的紧急程度不一样。
React 不能把所有更新都当成同步任务直接跑完,否则 Concurrent Mode 的意义就没了。
第二个问题:当前 Root 有没有已经存在的调度任务?
如果 Root 上已经有一个 callbackNode,而且新来的更新优先级没有变化,那么 React 没必要重复注册任务。
第三个问题:当前是否适合马上执行?
如果现在处在 render 或 commit 阶段,贸然同步执行可能导致重入问题。
所以 React 要做的不是"立刻 render",而是"确保 Root 被正确调度"。
这就是 ensureRootIsScheduled 名字的含义:
不是执行 Root,而是确保 Root 后续会以合适的优先级被执行。
四、React 18 中的 ensureRootIsScheduled
先看 React 18 的模型。
React 18 里,ensureRootIsScheduled(root, currentTime) 主要逻辑是:
function ensureRootIsScheduled(root, currentTime) {
const existingCallbackNode = root.callbackNode
markStarvedLanesAsExpired(root, currentTime)
const nextLanes = getNextLanes(root, ...)
if (nextLanes === NoLanes) {
cancelCallback(existingCallbackNode)
root.callbackNode = null
root.callbackPriority = NoLane
return
}
const newCallbackPriority = getHighestPriorityLane(nextLanes)
const existingCallbackPriority = root.callbackPriority
if (existingCallbackPriority === newCallbackPriority) {
return
}
if (existingCallbackNode !== null) {
cancelCallback(existingCallbackNode)
}
if (newCallbackPriority === SyncLane) {
scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root))
scheduleMicrotask(flushSyncCallbacks)
newCallbackNode = null
} else {
const schedulerPriorityLevel = lanesToSchedulerPriority(nextLanes)
newCallbackNode = scheduleCallback(
schedulerPriorityLevel,
performConcurrentWorkOnRoot.bind(null, root)
)
}
root.callbackPriority = newCallbackPriority
root.callbackNode = newCallbackNode
}
React 18 的 scheduleUpdateOnFiber 会调用 markRootUpdated,随后调用 ensureRootIsScheduled;而 React 18 的 ensureRootIsScheduled 内部会直接计算 nextLanes、复用或取消已有 callback,并根据优先级选择同步队列或 Scheduler callback。React 18 源码中可以看到 scheduleUpdateOnFiber 后面直接调用 ensureRootIsScheduled(root, eventTime),而 ensureRootIsScheduled 内部会根据 newCallbackPriority === SyncLane 走同步队列,否则通过 scheduleCallback 注册 performConcurrentWorkOnRoot。
这里有几个非常重要的点。
1. React 18 的 ensureRootIsScheduled 是真正的调度决策点
它不是只挂个标记。
它会直接算:
const nextLanes = getNextLanes(root, ...)
然后根据 nextLanes 找出:
const newCallbackPriority = getHighestPriorityLane(nextLanes)
如果新优先级和旧优先级一样,就复用之前的任务。
如果不一样,就取消旧任务,注册新任务。
2. SyncLane 不直接走 Scheduler
React 18 里,SyncLane 是特殊处理的。
它不会像普通并发任务那样直接进入 Scheduler,而是进入 React 自己维护的同步队列:
scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root))
然后通过微任务去 flush:
scheduleMicrotask(flushSyncCallbacks)
所以 React 18 中有一个很典型的分叉:
SyncLane
进入 sync callback queue
微任务 flushSyncCallbacks
执行 performSyncWorkOnRoot
Non SyncLane
Scheduler.scheduleCallback
执行 performConcurrentWorkOnRoot
这也是你之前容易混乱的地方。
不是所有更新都直接进 Scheduler。
在 React 18 里,同步更新有 React 内部自己的同步队列路径。
3. performConcurrentWorkOnRoot 是并发任务入口
对于非同步任务,React 18 会注册:
scheduleCallback(
schedulerPriorityLevel,
performConcurrentWorkOnRoot.bind(null, root)
)
后面 Scheduler 在合适的时机回调它。
然后 performConcurrentWorkOnRoot 里面才会进入:
renderRootConcurrent
或者在某些情况下退化成:
renderRootSync
所以 React 18 的链路可以概括成:
scheduleUpdateOnFiber
markRootUpdated
ensureRootIsScheduled
getNextLanes
getHighestPriorityLane
根据优先级注册任务
performSyncWorkOnRoot 或 performConcurrentWorkOnRoot
renderRootSync 或 renderRootConcurrent
workLoop
beginWork
completeWork
commitRoot
五、React 19 中的 ensureRootIsScheduled 变了
React 19 最大的变化是:
ensureRootIsScheduled 不再直接承担完整的调度决策。
它现在更像是一个 Root schedule 的入口函数。
React 19 中大概是这样:
export function ensureRootIsScheduled(root) {
if (root === lastScheduledRoot || root.next !== null) {
} else {
if (lastScheduledRoot === null) {
firstScheduledRoot = lastScheduledRoot = root
} else {
lastScheduledRoot.next = root
lastScheduledRoot = root
}
}
mightHavePendingSyncWork = true
ensureScheduleIsScheduled()
}
这段逻辑的重点不是 getNextLanes,也不是 scheduleCallback,而是两个动作:
第一,把 Root 加入一个全局 Root 链表。
第二,确保有一个微任务去处理这个 Root 链表。
React 19 main 分支源码中,firstScheduledRoot 和 lastScheduledRoot 用来维护所有有 pending work 的 Root 链表;ensureRootIsScheduled 会把 Root 放入链表,设置 mightHavePendingSyncWork,然后调用 ensureScheduleIsScheduled 安排后续处理。
所以 React 19 的 ensureRootIsScheduled 可以理解成:
先不要急着算优先级。
先把 Root 收集起来。
等当前事件结束后的微任务里,再统一处理所有 Root。
六、为什么 React 19 要多一层 Root 微任务调度
这个变化很关键。
React 18 的模型是:
每次 update 进来,ensureRootIsScheduled 都尝试立即对这个 Root 做一次调度判断。
React 19 的模型是:
每次 update 进来,先把 Root 放进 Root schedule,确保有微任务,真正的调度判断放到微任务里统一处理。
为什么这样更合理?
因为一次浏览器事件里,可能连续触发多个更新。
比如:
function handleClick() {
setA(1)
setB(2)
setC(3)
}
这三个更新会连续进入 React。
如果每次都立刻完整计算 Root 调度,会产生重复判断。
React 更希望:
当前事件里发生的更新先合并到 Root 的 pendingLanes 上。
事件同步代码执行完后,进入微任务。
在微任务里统一看 Root 上最终有哪些 pendingLanes。
再决定到底要同步 flush,还是注册并发任务。
这就是 React 19 Root 微任务调度的意义。
它不是为了"让所有更新都变成异步"。
而是为了把 Root 级别的调度决策延后到一个更稳定的时间点。
七、ensureScheduleIsScheduled:确保只安排一个微任务
React 19 中 ensureRootIsScheduled 会调用:
ensureScheduleIsScheduled()
它的核心逻辑大概是:
export function ensureScheduleIsScheduled() {
if (__DEV__ && ReactSharedInternals.actQueue !== null) {
if (!didScheduleMicrotask_act) {
didScheduleMicrotask_act = true
scheduleImmediateRootScheduleTask()
}
} else {
if (!didScheduleMicrotask) {
didScheduleMicrotask = true
scheduleImmediateRootScheduleTask()
}
}
}
这里有两个标记:
didScheduleMicrotask
didScheduleMicrotask_act
它们的作用是防止重复安排微任务。
比如同一个事件里连续 setState 三次:
setA(1)
setB(2)
setC(3)
第一次更新进来时,React 安排一个微任务。
第二次、第三次更新进来时,发现已经有 pending microtask,就不会重复安排。
所以微任务不是每个 update 一个,而是当前事件周期内多个 update 共享一个 Root schedule microtask。
这就是 batching 和 Root schedule 之间的关系。
批处理不是简单地说"setState 异步了",而是 React 会把同一个事件周期内的多个更新先记录到 Root 上,然后在统一的调度点处理它们。
八、scheduleImmediateRootScheduleTask:真正安排微任务
继续往下看:
function scheduleImmediateRootScheduleTask() {
if (supportsMicrotasks) {
scheduleMicrotask(() => {
const executionContext = getExecutionContext()
if ((executionContext & (RenderContext | CommitContext)) !== NoContext) {
Scheduler_scheduleCallback(
ImmediateSchedulerPriority,
processRootScheduleInImmediateTask
)
return
}
processRootScheduleInMicrotask()
})
} else {
Scheduler_scheduleCallback(
ImmediateSchedulerPriority,
processRootScheduleInImmediateTask
)
}
}
这段代码说明了几个问题。
1. 优先使用微任务
如果当前环境支持微任务,React 会通过 scheduleMicrotask 安排:
processRootScheduleInMicrotask()
也就是说,React 19 里 Root schedule 的处理默认发生在微任务中。
2. 如果正在 render 或 commit,就不能直接处理
源码里会判断:
executionContext & (RenderContext | CommitContext)
如果当前正处于 render 或 commit 阶段,就不会直接执行 processRootScheduleInMicrotask,而是退回到 Scheduler 的 ImmediatePriority task。
原因是 React 不允许在 render 或 commit 中间随便插入新的 Root schedule flush。
否则可能出现重入问题。
3. 不支持微任务时,退回 Scheduler
如果环境不支持 microtask,就走:
Scheduler_scheduleCallback(
ImmediateSchedulerPriority,
processRootScheduleInImmediateTask
)
所以 React 19 不是完全依赖微任务,而是优先微任务,不行再退回 Scheduler。
React 19 源码中 scheduleImmediateRootScheduleTask 会优先使用 scheduleMicrotask,并在 render 或 commit 上下文中转而通过 Scheduler 的 ImmediatePriority 处理,避免在渲染或提交中间重入执行 Root schedule。
九、processRootScheduleInMicrotask:统一处理所有 scheduled roots
进入微任务后,React 会执行:
processRootScheduleInMicrotask()
这个函数非常关键。
它做的事情大概是:
function processRootScheduleInMicrotask() {
didScheduleMicrotask = false
mightHavePendingSyncWork = false
const currentTime = now()
let prev = null
let root = firstScheduledRoot
while (root !== null) {
const next = root.next
const nextLanes = scheduleTaskForRootDuringMicrotask(root, currentTime)
if (nextLanes === NoLane) {
removeRootFromSchedule(root)
} else {
keepRootInSchedule(root)
if (includesSyncLane(nextLanes)) {
mightHavePendingSyncWork = true
}
}
root = next
}
flushSyncWorkAcrossRoots_impl(...)
}
这个函数的名字有点误导。
它不是"执行所有 Root 的渲染"。
它的主要职责是:
遍历所有被 schedule 的 Root。
对每个 Root 调用 scheduleTaskForRootDuringMicrotask。
如果 Root 没活了,就从链表移除。
如果 Root 还有活,就保留在链表中。
最后统一 flush 同步任务。
这也是 React 19 和 React 18 很大的区别。
React 18 是每个 Root 在 ensureRootIsScheduled 里直接判断和注册任务。
React 19 是把所有 Root 放进链表,然后在微任务中统一处理。
React 19 源码中 processRootScheduleInMicrotask 会重置微任务标记,遍历 firstScheduledRoot 链表,并对每个 Root 调用 scheduleTaskForRootDuringMicrotask;如果某个 Root 没有剩余工作,会在这个微任务中从 schedule 链表移除。
十、scheduleTaskForRootDuringMicrotask:真正的调度决策点
React 19 中,真正接近 React 18 ensureRootIsScheduled 角色的,是这个函数:
scheduleTaskForRootDuringMicrotask(root, currentTime)
它的核心逻辑是:
function scheduleTaskForRootDuringMicrotask(root, currentTime) {
markStarvedLanesAsExpired(root, currentTime)
const nextLanes = getNextLanes(root, ...)
const existingCallbackNode = root.callbackNode
if (nextLanes === NoLanes || root is suspended) {
cancelCallback(existingCallbackNode)
root.callbackNode = null
root.callbackPriority = NoLane
return NoLane
}
if (includesSyncLane(nextLanes)) {
cancelCallback(existingCallbackNode)
root.callbackPriority = SyncLane
root.callbackNode = null
return SyncLane
}
const newCallbackPriority = getHighestPriorityLane(nextLanes)
const existingCallbackPriority = root.callbackPriority
if (newCallbackPriority === existingCallbackPriority) {
return newCallbackPriority
}
cancelCallback(existingCallbackNode)
const schedulerPriorityLevel = lanesToSchedulerPriority(nextLanes)
const newCallbackNode = scheduleCallback(
schedulerPriorityLevel,
performWorkOnRootViaSchedulerTask.bind(null, root)
)
root.callbackPriority = newCallbackPriority
root.callbackNode = newCallbackNode
return newCallbackPriority
}
这个函数才开始真正决定:
Root 还有没有活。
该处理哪些 lanes。
旧 callback 能不能复用。
同步任务要不要直接交给微任务末尾 flush。
并发任务要不要注册到 Scheduler。
React 19 源码中 scheduleTaskForRootDuringMicrotask 的注释说明它应该只在微任务中,或在渲染任务即将让出主线程前调用,并且它本身不会同步执行 React work,只负责安排后续任务。
这句话非常重要。
scheduleTaskForRootDuringMicrotask 虽然名字里有 task,但它本身也不是真的开始 render。
它只是调度决策点。
真正干活的是后面的:
performSyncWorkOnRoot
或者:
performWorkOnRootViaSchedulerTask
十一、React 19 中 SyncLane 怎么处理
React 19 对同步任务的处理方式和 React 18 不一样。
React 18 中 SyncLane 会进入:
scheduleSyncCallback(...)
scheduleMicrotask(flushSyncCallbacks)
React 19 中,如果 nextLanes 包含 SyncLane,在 scheduleTaskForRootDuringMicrotask 中会这样处理:
if (includesSyncLane(nextLanes)) {
if (existingCallbackNode !== null) {
cancelCallback(existingCallbackNode)
}
root.callbackPriority = SyncLane
root.callbackNode = null
return SyncLane
}
注意,这里没有注册 Scheduler task。
为什么?
因为 React 19 的同步任务会在 processRootScheduleInMicrotask 的末尾统一 flush:
flushSyncWorkAcrossRoots_impl(...)
也就是说,同步更新的链路变成了:
scheduleUpdateOnFiber
markRootUpdated
ensureRootIsScheduled
把 Root 放入 schedule 链表
安排微任务
processRootScheduleInMicrotask
scheduleTaskForRootDuringMicrotask 发现 SyncLane
返回 SyncLane
微任务末尾 flushSyncWorkAcrossRoots_impl
performSyncWorkOnRoot
performWorkOnRoot
所以 React 19 中的同步任务没有像 React 18 那样先进入 sync callback queue,再 flushSyncCallbacks。
它是在 Root schedule 微任务末尾,跨 Root 统一 flush 同步工作。
这就是 React 19 调度模型更集中化的地方。
十二、React 19 中并发任务怎么处理
如果不是 SyncLane,就会走 Scheduler:
const newCallbackNode = scheduleCallback(
schedulerPriorityLevel,
performWorkOnRootViaSchedulerTask.bind(null, root)
)
这里的 schedulerPriorityLevel 由 lanes 转换而来:
switch (lanesToEventPriority(nextLanes)) {
case DiscreteEventPriority:
case ContinuousEventPriority:
schedulerPriorityLevel = UserBlockingSchedulerPriority
break
case DefaultEventPriority:
schedulerPriorityLevel = NormalSchedulerPriority
break
case IdleEventPriority:
schedulerPriorityLevel = IdleSchedulerPriority
break
default:
schedulerPriorityLevel = NormalSchedulerPriority
break
}
所以这里有两套优先级体系:
React 内部用的是 lane。
Scheduler 用的是 scheduler priority。
React 要先通过 getNextLanes 决定 Root 下一批要处理的 lanes,再通过 lanesToEventPriority 映射成 Scheduler 能理解的优先级。
这也是为什么不能把 lane 和 Scheduler priority 混为一谈。
lane 是 React Reconciler 内部描述更新集合和优先级的模型。
Scheduler priority 是调度器执行 callback 时使用的优先级模型。
它们有关联,但不是同一个东西。
十三、performWorkOnRootViaSchedulerTask:Scheduler 回调入口
当 Scheduler 到时间执行任务时,会调用:
performWorkOnRootViaSchedulerTask(root, didTimeout)
这个函数是 React 19 中并发任务通过 Scheduler 回到 Reconciler 的入口。
它大概做这些事:
function performWorkOnRootViaSchedulerTask(root, didTimeout) {
const originalCallbackNode = root.callbackNode
const didFlushPassiveEffects = flushPendingEffectsDelayed()
if (didFlushPassiveEffects) {
if (root.callbackNode !== originalCallbackNode) {
return null
}
}
const lanes = getNextLanes(root, ...)
if (lanes === NoLanes) {
return null
}
const forceSync = didTimeout
performWorkOnRoot(root, lanes, forceSync)
scheduleTaskForRootDuringMicrotask(root, now())
if (root.callbackNode != null && root.callbackNode === originalCallbackNode) {
return performWorkOnRootViaSchedulerTask.bind(null, root)
}
return null
}
这里开始真正接近渲染阶段。
因为它会调用:
performWorkOnRoot(root, lanes, forceSync)
performWorkOnRoot 内部才会根据 lanes 和当前模式进入同步或并发 work loop。
也就是说:
scheduleUpdateOnFiber
不是渲染入口。
ensureRootIsScheduled
也不是渲染入口。
scheduleTaskForRootDuringMicrotask
仍然不是渲染入口。
对于并发任务来说,真正通过 Scheduler 回到 React 并进入 work loop 的入口是:
performWorkOnRootViaSchedulerTask
对于同步任务来说,则是:
performSyncWorkOnRoot
而这两个最终都会进入:
performWorkOnRoot
React 19 源码中 performWorkOnRootViaSchedulerTask 是 Scheduler 任务的入口,它会先处理 pending passive effects,再重新计算 lanes,最后调用 performWorkOnRoot(root, lanes, forceSync) 进入真正的 work loop。
十四、Root.callbackNode 和 Root.callbackPriority 是干嘛的
你前面问过 root.callback 相关的问题,这里正好接上。
在 React 的 Root 上,有两个很关键的调度字段:
root.callbackNode
root.callbackPriority
它们的作用是记录当前 Root 已经注册过的调度任务。
callbackNode
callbackNode 是 Scheduler 返回的任务节点。
例如:
const newCallbackNode = scheduleCallback(...)
root.callbackNode = newCallbackNode
以后如果来了一个更高优先级更新,React 可以取消旧任务:
cancelCallback(existingCallbackNode)
如果新更新优先级和旧任务一样,React 可以复用旧任务,不重复注册。
callbackPriority
callbackPriority 记录当前 callback 对应的 lane 优先级。
例如:
root.callbackPriority = newCallbackPriority
下次调度时,React 会比较:
existingCallbackPriority === newCallbackPriority
如果一样,就说明当前已有任务足够处理这批更新。
如果不一样,就需要取消旧任务,注册新任务。
所以这两个字段解决的是:
当前 Root 有没有已经安排好的任务?
这个任务的优先级和最新 pendingLanes 是否匹配?
需不需要取消重排?
这也是为什么我之前说:
ensureRootIsScheduled 的核心不是简单"安排任务",而是"确保 Root 的调度状态与 pendingLanes 匹配"。
在 React 18 中,这句话主要对应 ensureRootIsScheduled。
在 React 19 中,这句话更准确地对应:
ensureRootIsScheduled
processRootScheduleInMicrotask
scheduleTaskForRootDuringMicrotask
这一组函数共同完成的事情。
十五、React 18 和 React 19 的核心差异
React 18
React 18 的链路更直接:
scheduleUpdateOnFiber
markRootUpdated
ensureRootIsScheduled
getNextLanes
getHighestPriorityLane
复用或取消 callback
SyncLane 进入 sync callback queue
Non SyncLane 进入 Scheduler
performSyncWorkOnRoot 或 performConcurrentWorkOnRoot
特点是:
ensureRootIsScheduled 本身就是主要调度决策点。
同步任务通过 scheduleSyncCallback 加入内部同步队列。
并发任务通过 Scheduler 注册 performConcurrentWorkOnRoot。
React 19
React 19 的链路变成:
scheduleUpdateOnFiber
markRootUpdated
ensureRootIsScheduled
把 Root 加入 scheduled root 链表
ensureScheduleIsScheduled
scheduleImmediateRootScheduleTask
processRootScheduleInMicrotask
scheduleTaskForRootDuringMicrotask
SyncLane 在微任务末尾统一 flush
Non SyncLane 注册 Scheduler task
performSyncWorkOnRoot 或 performWorkOnRootViaSchedulerTask
performWorkOnRoot
特点是:
ensureRootIsScheduled 变轻了。
Root 会被放进全局 schedule 链表。
真正的 lanes 计算和 callback 注册延后到微任务中。
同步任务在 Root schedule 微任务末尾统一 flush。
并发任务通过 performWorkOnRootViaSchedulerTask 进入 Reconciler。
十六、把这条链路用一句话讲清楚
如果是 React 18,可以这样说:
scheduleUpdateOnFiber 把更新 lane 标记到 Root 上,然后通过 ensureRootIsScheduled 立即计算 Root 下一批要处理的 lanes,并根据优先级选择同步队列或 Scheduler 任务。
如果是 React 19,可以这样说:
scheduleUpdateOnFiber 把更新 lane 标记到 Root 上,然后通过 ensureRootIsScheduled 把 Root 加入全局调度链表,并安排一个微任务;在微任务里统一遍历所有 Root,计算 nextLanes,决定同步 flush 还是注册 Scheduler 任务。
这两个版本的关键差异不是"有没有 Scheduler",而是:
React 18 更像是每次 update 进来就直接对当前 Root 做调度判断。
React 19 更像是先收集 Root,再在微任务中统一做 Root 级别的调度判断。
十七、完整调用链
以 React 19 的初次渲染为例,链路大概是:
createRoot(container)
创建 FiberRootNode
创建 HostRootFiber
root.current = hostRootFiber
hostRootFiber.stateNode = root
root.render(<App />)
updateContainer(<App />, root)
createUpdate(lane)
update.payload = { element: <App /> }
enqueueUpdate(hostRootFiber, update, lane)
scheduleUpdateOnFiber(root, hostRootFiber, lane)
markRootUpdated(root, lane)
ensureRootIsScheduled(root)
把 root 加入 firstScheduledRoot 链表
mightHavePendingSyncWork = true
ensureScheduleIsScheduled()
scheduleImmediateRootScheduleTask()
scheduleMicrotask(processRootScheduleInMicrotask)
processRootScheduleInMicrotask()
遍历 scheduled roots
scheduleTaskForRootDuringMicrotask(root, now())
getNextLanes(root, ...)
计算下一批要处理的 lanes
如果是 SyncLane
返回 SyncLane
微任务末尾 flushSyncWorkAcrossRoots_impl
performSyncWorkOnRoot(root, lanes)
performWorkOnRoot(root, lanes, true)
如果是并发任务
scheduleCallback(priority, performWorkOnRootViaSchedulerTask)
Scheduler 回调
performWorkOnRootViaSchedulerTask(root)
performWorkOnRoot(root, lanes, false)
进入 render 阶段
prepareFreshStack
workLoopSync 或 workLoopConcurrent
performUnitOfWork
beginWork
completeWork
进入 commit 阶段
commitRoot
commitMutationEffects
commitLayoutEffects
到这里,App 才真正开始在 beginWork 中被消费,进而转成 Fiber 子节点挂到 workInProgress tree 上。
所以你前面理解的那句话是对的:
root.render 不是直接渲染。
它只是把 App 作为一次 update 放到 HostRootFiber.updateQueue 里。
真正让这次 update 进入 render 阶段,需要经过 scheduleUpdateOnFiber、Root 调度、微任务处理、Scheduler 或同步 flush,最后才进入 work loop。