上一篇我们讲到了,root.render()最后调用scheduleUpdateOnFiber注册开始任务调度,本篇我们介绍下scheduleUpdateOnFiber做了什么
scheduleUpdateOnFiber 函数的作用
scheduleUpdateOnFiber 是 React 调度系统的核心入口函数,它负责将更新转换为可执行的任务并进行调度。
函数签名
export function scheduleUpdateOnFiber(
root: FiberRoot, // Fiber 根节点
fiber: Fiber, // 发生更新的 Fiber 节点
lane: Lane, // 更新的优先级
eventTime: number, // 事件时间戳
)
主要工作步骤
第一步:安全检查和标记
checkForNestedUpdates(); // 检查嵌套更新
markRootUpdated(root, lane, eventTime); // 标记根节点有更新
第二步:区分渲染阶段更新 vs 正常更新
if (
(executionContext & RenderContext) !== NoLanes &&
root === workInProgressRoot
) {
warnAboutRenderPhaseUpdatesInDEV(fiber);
workInProgressRootRenderPhaseUpdatedLanes = mergeLanes(
workInProgressRootRenderPhaseUpdatedLanes,
lane,
);
} else {
if (enableUpdaterTracking) {
if (isDevToolsPresent) {
addFiberToLanesMap(root, fiber, lane);
}
}
}
第三步:处理中间更新(Interleaved Updates)
if (root === workInProgressRoot) {
if (
deferRenderPhaseUpdateToNextBatch ||
(executionContext & RenderContext) === NoContext
) {
workInProgressRootInterleavedUpdatedLanes = mergeLanes(
workInProgressRootInterleavedUpdatedLanes,
lane,
);
}
if (workInProgressRootExitStatus === RootSuspendedWithDelay) {
markRootSuspended(root, workInProgressRootRenderLanes);
}
}
第四步:核心调度 - ensureRootIsScheduled
ensureRootIsScheduled(root, eventTime);
console.log("✅ ~ ensureRootIsScheduled:", ensureRootIsScheduled)
-
ensureRootIsScheduled是整个函数的 核心。 -
它负责:
-
确定下一个要处理的优先级(
getNextLanes) -
检查是否可以重用现有任务
-
根据优先级选择调度策略:
- 同步更新 →
scheduleSyncCallback/scheduleLegacySyncCallback - 并发更新 →
scheduleCallback并传入调度器优先级
- 同步更新 →
-
第五步:处理同步更新的特殊情况
if (
lane === SyncLane &&
executionContext === NoContext &&
(fiber.mode & ConcurrentMode) === NoMode &&
!(__DEV__ && ReactCurrentActQueue.isBatchingLegacy)
) {
resetRenderTimer();
flushSyncCallbacksOnlyInLegacyMode();
}
ensureRootIsScheduled 的核心逻辑
同步调度
if (newCallbackPriority === SyncLane) {
if (root.tag === LegacyRoot) {
scheduleLegacySyncCallback(performSyncWorkOnRoot.bind(null, root));
} else {
scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root));
}
}
并发调度
else {
let schedulerPriorityLevel;
switch (lanesToEventPriority(nextLanes)) {
case DiscreteEventPriority:
schedulerPriorityLevel = ImmediateSchedulerPriority;
break;
case ContinuousEventPriority:
schedulerPriorityLevel = UserBlockingSchedulerPriority;
break;
case DefaultEventPriority:
schedulerPriorityLevel = NormalSchedulerPriority;
break;
case IdleEventPriority:
schedulerPriorityLevel = IdleSchedulerPriority;
break;
default:
schedulerPriorityLevel = NormalSchedulerPriority;
break;
}
newCallbackNode = scheduleCallback(
schedulerPriorityLevel,
performConcurrentWorkOnRoot.bind(null, root),
);
}
总结
scheduleUpdateOnFiber 的核心作用:
-
更新调度入口:将 Fiber 更新转换为可调度任务
-
优先级管理:根据 Lane 确定更新优先级
-
调度策略选择:
- 同步更新 →
performSyncWorkOnRoot - 并发更新 →
performConcurrentWorkOnRoot
- 同步更新 →
-
任务队列管理:将任务添加到相应调度队列
-
中断与恢复:处理渲染过程中的新更新
整体流程:
scheduleUpdateOnFiber
↓
ensureRootIsScheduled
↓
优先级判断 → 同步/并发调度
↓
performSyncWorkOnRoot / performConcurrentWorkOnRoot
↓
实际的渲染工作循环
首次 root.render 注册同步任务
首次调用:
root.render(<App />);
流程概览
- React 创建 根 Fiber 对应
<App />。 - React 创建 更新对象(Update) 。
- 调用
enqueueUpdate(fiber, update)将更新加入 Fiber 更新队列。 - 调用:
scheduleUpdateOnFiber(root, root.current, SyncLane, eventTime);
首次渲染使用 SyncLane(同步更新) 。
scheduleUpdateOnFiber 注册任务的逻辑
- 获取下一个要执行的 Lane:
const nextLanes = getNextLanes(root); // 首次渲染只有 SyncLane
- 判断同步调度:
if (newCallbackPriority === SyncLane) {
if (root.tag === LegacyRoot) {
scheduleLegacySyncCallback(performSyncWorkOnRoot.bind(null, root));
} else {
scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root));
}
}
- ConcurrentRoot(React18+) →
scheduleSyncCallback(performSyncWorkOnRoot(root)) - LegacyRoot(React17 及以前) →
scheduleLegacySyncCallback(performSyncWorkOnRoot(root))
✅ 首次渲染注册的是 同步任务,直接执行 performSyncWorkOnRoot,构建 Fiber 树并提交 DOM。
任务类型总结
| 场景 | Root 类型 | Lane | 注册任务函数 | 调度方式 |
|---|---|---|---|---|
| 首次 render / React18+ | ConcurrentRoot | SyncLane | performSyncWorkOnRoot(root) | 同步调度 |
| 首次 render / React17 | LegacyRoot | SyncLane | performSyncWorkOnRoot(root) | 同步队列 |
| 用户事件触发更新 | ConcurrentRoot | 非SyncLane | performConcurrentWorkOnRoot(root) | 并发调度 |
执行顺序
root.render(<App />)
↓
enqueueUpdate(fiber, update)
↓
scheduleUpdateOnFiber(root, fiber, SyncLane)
↓
ensureRootIsScheduled
↓
scheduleSyncCallback(performSyncWorkOnRoot(root))
↓
执行 performSyncWorkOnRoot → 构建 Fiber 树 → 提交 DOM
⚡ 首次
root.render不会进入并发队列,它是一个 同步任务,保证页面立即渲染。
小结
scheduleUpdateOnFiber是 React 从 更新到调度任务 的核心函数。- 首次
root.render使用 同步 Lane,注册performSyncWorkOnRoot同步任务。 - 同步任务保证首次渲染立即完成,而并发更新机制从第二次更新开始发挥作用。
- 了解首次渲染任务机制,有助于理解 React 并发模式与同步渲染的差异。
注册task的几个schedule源码剖析
在 React 的内部实现中,任务调度是核心机制之一。React 需要协调同步更新和异步更新,处理不同优先级的任务,并确保良好的用户体验。本文将深入分析 React 中三个关键的调度函数:scheduleSyncCallback、scheduleLegacySyncCallback 和 scheduleCallback,通过源码解读来理解 React 调度系统的设计思想。
1. 概述:三个调度函数的定位
在 React 的调度体系中,这三个函数承担着不同的职责:
scheduleSyncCallback:现代 React 的同步更新调度scheduleLegacySyncCallback:兼容 Legacy 模式的同步更新调度scheduleCallback:基于优先级的异步任务调度
它们的关系可以用这个图来表示:
React 更新调度
│
├─ 同步更新 (SyncLane)
│ ├─ Legacy 模式 → scheduleLegacySyncCallback
│ └─ 现代模式 → scheduleSyncCallback
│ │
│ └─ 内部队列 (syncQueue) + 微任务/宏任务刷新
│
└─ 并发更新 (其他 Lane)
└─ scheduleCallback → React Scheduler → 优先级调度
2. scheduleSyncCallback:同步任务队列管理
2.1 源码实现
// src/react/packages/react-reconciler/src/ReactFiberSyncTaskQueue.new.js
let syncQueue: Array<SchedulerCallback> | null = null;
let includesLegacySyncCallbacks: boolean = false;
let isFlushingSyncQueue: boolean = false;
export function scheduleSyncCallback(callback: SchedulerCallback) {
// Push this callback into an internal queue. We'll flush these either in
// the next tick, or earlier if something calls `flushSyncCallbackQueue`.
if (syncQueue === null) {
syncQueue = [callback];
} else {
// Push onto existing queue. Don't need to schedule a callback because
// we already scheduled one when we created the queue.
syncQueue.push(callback);
}
}
2.2 核心特点
- 队列管理:维护一个内部的
syncQueue数组 - 延迟执行:只是将回调加入队列,不立即执行
- 批处理优化:多个同步任务可以在一次刷新中批量处理
- 避免重复调度:后续任务直接加入现有队列
2.3 使用场景
// 在 ReactFiberWorkLoop.new.js 中的调用
if (newCallbackPriority === SyncLane) {
if (root.tag === LegacyRoot) {
scheduleLegacySyncCallback(performSyncWorkOnRoot.bind(null, root));
} else {
scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root)); // 这里
}
}
3. scheduleLegacySyncCallback:Legacy 模式适配
3.1 源码实现
export function scheduleLegacySyncCallback(callback: SchedulerCallback) {
includesLegacySyncCallbacks = true;
scheduleSyncCallback(callback);
}
3.2 设计目的
这个函数看似简单,但承担着重要的兼容性职责:
- 标记 Legacy 模式:设置
includesLegacySyncCallbacks = true - 复用现有逻辑:内部调用
scheduleSyncCallback - 区分处理策略:为不同模式提供差异化的刷新逻辑
3.3 Legacy 模式的特殊处理
export function flushSyncCallbacksOnlyInLegacyMode() {
// Only flushes the queue if there's a legacy sync callback scheduled.
if (includesLegacySyncCallbacks) {
flushSyncCallbacks();
}
}
这个函数只会在包含 Legacy 回调时才刷新队列,体现了对不同渲染模式的精细控制。
4. scheduleCallback:优先级调度器
4.1 React 中的包装层
// src/react/packages/react-reconciler/src/ReactFiberWorkLoop.new.js
function scheduleCallback(priorityLevel, callback) {
if (__DEV__) {
// If we're currently inside an `act` scope, bypass Scheduler and push to
// the `act` queue instead.
const actQueue = ReactCurrentActQueue.current;
if (actQueue !== null) {
actQueue.push(callback);
return fakeActCallbackNode;
} else {
return Scheduler_scheduleCallback(priorityLevel, callback);
}
} else {
// In production, always call Scheduler. This function will be stripped out.
return Scheduler_scheduleCallback(priorityLevel, callback);
}
}
4.2 Scheduler 的核心实现
// src/react/packages/scheduler/src/forks/Scheduler.js
function unstable_scheduleCallback(priorityLevel, callback, options) {
var currentTime = getCurrentTime();
var startTime;
if (typeof options === 'object' && options !== null) {
var delay = options.delay;
if (typeof delay === 'number' && delay > 0) {
startTime = currentTime + delay;
} else {
startTime = currentTime;
}
} else {
startTime = currentTime;
}
var timeout;
switch (priorityLevel) {
case ImmediatePriority:
timeout = IMMEDIATE_PRIORITY_TIMEOUT;
break;
case UserBlockingPriority:
timeout = USER_BLOCKING_PRIORITY_TIMEOUT;
break;
case IdlePriority:
timeout = IDLE_PRIORITY_TIMEOUT;
break;
case LowPriority:
timeout = LOW_PRIORITY_TIMEOUT;
break;
case NormalPriority:
default:
timeout = NORMAL_PRIORITY_TIMEOUT;
break;
}
var expirationTime = startTime + timeout;
var newTask = {
id: taskIdCounter++,
callback,
priorityLevel,
startTime,
expirationTime,
sortIndex: -1,
};
// 任务调度逻辑...
}
4.3 调度机制
scheduleCallback 使用宏任务进行调度,主要通过:
- MessageChannel:优先选择,避免 4ms 延迟
- setImmediate:Node.js 环境
- setTimeout:兜底方案
let schedulePerformWorkUntilDeadline;
if (typeof localSetImmediate === 'function') {
schedulePerformWorkUntilDeadline = () => {
localSetImmediate(performWorkUntilDeadline);
};
} else if (typeof MessageChannel !== 'undefined') {
const channel = new MessageChannel();
const port = channel.port2;
channel.port1.onmessage = performWorkUntilDeadline;
schedulePerformWorkUntilDeadline = () => {
port.postMessage(null);
};
} else {
schedulePerformWorkUntilDeadline = () => {
localSetTimeout(performWorkUntilDeadline, 0);
};
}
5. 同步队列的执行:flushSyncCallbacks
5.1 源码实现
export function flushSyncCallbacks() {
if (!isFlushingSyncQueue && syncQueue !== null) {
// Prevent re-entrance.
isFlushingSyncQueue = true;
let i = 0;
const previousUpdatePriority = getCurrentUpdatePriority();
try {
const isSync = true;
const queue = syncQueue;
setCurrentUpdatePriority(DiscreteEventPriority);
for (; i < queue.length; i++) {
let callback = queue[i];
do {
callback = callback(isSync);
} while (callback !== null);
}
syncQueue = null;
includesLegacySyncCallbacks = false;
} catch (error) {
// If something throws, leave the remaining callbacks on the queue.
if (syncQueue !== null) {
syncQueue = syncQueue.slice(i + 1);
}
// Resume flushing in the next tick
scheduleCallback(ImmediatePriority, flushSyncCallbacks);
throw error;
} finally {
setCurrentUpdatePriority(previousUpdatePriority);
isFlushingSyncQueue = false;
}
}
return null;
}
5.2 关键特性
- 防重入:通过
isFlushingSyncQueue标志避免重复执行 - 优先级设置:使用
DiscreteEventPriority确保高优先级 - 错误处理:出错时保留剩余任务,并通过 Scheduler 恢复
- 连续执行:支持回调返回新回调的链式执行
6. 调度策略的选择:微任务 vs 宏任务
6.1 同步更新为什么选择微任务?
在 ReactFiberWorkLoop.new.js 中:
if (supportsMicrotasks) {
// Flush the queue in a microtask.
if (__DEV__ && ReactCurrentActQueue.current !== null) {
ReactCurrentActQueue.current.push(flushSyncCallbacks);
} else {
scheduleMicrotask(() => {
if (
(executionContext & (RenderContext | CommitContext)) ===
NoContext
) {
flushSyncCallbacks();
}
});
}
} else {
// Flush the queue in an Immediate task.
scheduleCallback(ImmediateSchedulerPriority, flushSyncCallbacks);
}
选择微任务的原因:
- 更快的执行时机:微任务在当前事件循环末尾立即执行
- 符合同步语义:用户调用
flushSync期望立即看到结果 - 避免不必要延迟:不用等待下一个宏任务循环
6.2 并发更新为什么选择宏任务?
并发更新通过 Scheduler 使用宏任务,原因是:
- 让出主线程:宏任务之间浏览器可以进行渲染
- 支持中断:可以在任务间检查更高优先级的更新
- 避免阻塞渲染:防止长时间占用主线程
7. 调度路径分析
7.1 并非所有调度都汇聚到 unstable_scheduleCallback
通过源码分析,我们发现:
调度入口分析:
│
├─ 同步更新路径 (独立系统)
│ ├─ scheduleSyncCallback → syncQueue → 微任务刷新
│ ├─ scheduleLegacySyncCallback → syncQueue → 微任务刷新
│ └─ 错误恢复时 → scheduleCallback → unstable_scheduleCallback
│
└─ 并发更新路径 (Scheduler 系统)
└─ scheduleCallback → Scheduler_scheduleCallback → unstable_scheduleCallback
7.2 设计优势
这种分离式设计带来的好处:
- 同步更新更快:避免 Scheduler 的调度开销
- 并发更新更灵活:支持复杂的优先级调度
- 错误恢复健壮:同步队列出错可降级到异步恢复
8. 实际使用示例
8.1 同步更新触发
// 用户代码
flushSync(() => {
setCount(count + 1);
});
// React 内部调用链
// → ensureRootIsScheduled
// → scheduleSyncCallback(performSyncWorkOnRoot)
// → scheduleMicrotask(flushSyncCallbacks)
// → 立即执行更新
8.2 并发更新触发
// 用户代码
startTransition(() => {
setLargeList(newData);
});
// React 内部调用链
// → ensureRootIsScheduled
// → scheduleCallback(NormalPriority, performConcurrentWorkOnRoot)
// → unstable_scheduleCallback
// → 基于优先级调度执行
9. 总结
-
scheduleSyncCallback和scheduleLegacySyncCallback构成了 React 的同步更新系统,使用内部队列管理,通过微任务快速刷新 -
scheduleCallback构成了 React 的异步调度系统,基于 Scheduler 实现优先级调度和时间切片 -
微任务和宏任务的选择不是任意的,而是基于具体场景的权衡:同步更新追求速度,并发更新追求流畅
参考
- React 源码:
packages/react-reconciler/src/ReactFiberWorkLoop.new.js - React 源码:
packages/react-reconciler/src/ReactFiberSyncTaskQueue.new.js - React 源码:
packages/scheduler/src/forks/Scheduler.js