深入学习React源码实现之调度器(Scheduler)底层实现
一、调度器(Scheduler)历史介绍
在 React 的早期版本中,所有的更新都是同步执行的。这意味着一旦触发 setState
或组件渲染,整个渲染过程会立即完成,期间无法中断,导致页面在复杂应用中出现卡顿。
为了解决这个问题,React 团队从 React 16 开始引入了 Fiber 架构 和 并发模式(Concurrent Mode),其中的关键组成部分就是 React 调度器(Scheduler)。
Scheduler 的作用:
- 实现任务优先级调度
- 支持时间片调度(Time Slicing)
- 允许中断和恢复长时间任务
- 提供基于浏览器 API(如 MessageChannel、setTimeout)的异步执行机制
相关源码文件:
文件路径 | 功能 |
---|---|
packages/scheduler/src/Scheduler.js | 核心调度逻辑 |
packages/scheduler/src/SchedulerPriorities.js | 定义 LanePriority 类型 |
packages/scheduler/src/forks/SchedulerHostConfig.default.js | 平台适配层(DOM 实现) |
react-reconciler/src/ReactFiberWorkLoop.old.js | 使用 Scheduler 的协调器入口 |
二、算法设计思路与核心概念
1. 设计目标
- 支持不同优先级任务调度
- 允许高优先级任务打断低优先级任务
- 使用时间片调度避免主线程阻塞
- 兼容多种平台(Web、Native)
2. 核心数据结构
(1) Task
对象结构
{
id: number, // 唯一标识符
callback: Function, // 回调函数
priorityLevel: number, // 优先级等级
startTime: number, // 可开始执行的时间戳
expirationTime: number, // 截止时间
sortIndex: number, // 排序依据
}
(2) 优先级枚举(LanePriority)
export const NoPriority = 0;
export const ImmediatePriority = 1; // 立即执行
export const UserBlockingPriority = 2; // 用户感知阻塞
export const NormalPriority = 3; // 正常优先级
export const LowPriority = 4; // 低优先级
export const IdlePriority = 5; // 空闲时才执行
📄 源码位置:
packages/scheduler/src/SchedulerPriorities.js
三、调度流程图解
requestIdleCallback / setTimeout
↓
创建 Task 并插入 taskQueue
↓
根据优先级排序(sortIndex)
↓
进入 workLoop 阶段
↓
判断是否超时或可继续执行
↓
执行回调函数
↓
返回 next 单位任务或 null(表示结束)
四、手动实现一个简化版 Scheduler
下面是一个简化版的 React Scheduler 实现,包含优先级调度、时间片控制、任务中断恢复等核心功能。
✅ 特性支持:
- 时间片调度(每帧最多执行 5ms)
- 优先级调度(Immediate > Blocking > Normal > Low > Idle)
- 支持中断并恢复任务
✨ 源码实现如下:
// 定义优先级常量
const NoPriority = 0; // 无优先级(通常用于占位)
const ImmediatePriority = 1; // 最高优先级(立即执行,如用户交互)
const UserBlockingPriority = 2; // 用户阻塞任务(如动画)
const NormalPriority = 3; // 默认优先级(如普通渲染)
const LowPriority = 4; // 低优先级(如非关键更新)
const IdlePriority = 5; // 空闲时执行(如日志上报)
// 任务队列(按优先级和截止时间排序)
let taskQueue = []; // 存储待执行的任务
let currentTask = null; // 当前正在执行的任务
let isPerformingWork = false; // 标记是否正在执行任务(防止重入)
// 获取当前时间戳(高精度时间)
function getCurrentTime() {
return performance.now(); // 返回当前时间戳(单位:毫秒)
}
// 插入任务到队列中(按优先级和截止时间排序)
function insertTask(task) {
// 计算任务的排序索引:优先按截止时间,其次按优先级
task.sortIndex = task.expirationTime !== 0 ? task.expirationTime : task.startTime + 5000; // 默认延迟5秒
// 遍历队列,找到合适的插入位置
let i = 0;
for (; i < taskQueue.length; i++) {
const existing = taskQueue[i];
if (
existing.priorityLevel > task.priorityLevel || // 优先级更高的任务在前
(existing.priorityLevel === task.priorityLevel && existing.sortIndex > task.sortIndex) // 优先级相同时,截止时间更早的任务在前
) {
break;
}
}
taskQueue.splice(i, 0, task); // 插入任务到正确位置
}
// 计算截止时间(当前时间 + 5ms)
let deadline = 0; // 截止时间(由 requestIdleCallback 动态设置)
function shouldYieldToHost() {
return getCurrentTime() >= deadline; // 检查是否超过截止时间(需要让出主线程)
}
// 执行单个任务
function performUnitOfWork(task) {
const result = task.callback(); // 执行任务回调
if (typeof result === 'function') {
task.callback = result; // 如果返回函数,则作为新的回调(支持任务拆分)
} else {
task.callback = null; // 否则清空回调(任务完成)
}
}
// 主工作循环
function workLoop(deadlineArg) {
// 设置截止时间:如果超时,则截止时间为最大值;否则为剩余时间 + 当前时间
deadline = deadlineArg.didTimeout ? Number.MAX_SAFE_INTEGER : deadlineArg.timeRemaining() + getCurrentTime();
// 循环执行任务,直到队列为空或需要让出主线程
while (taskQueue.length > 0 && !shouldYieldToHost()) {
currentTask = taskQueue[0]; // 获取队列中的第一个任务
// 检查任务是否过期(强制执行)
if (currentTask.expirationTime !== 0 && currentTask.expirationTime <= getCurrentTime()) {
performUnitOfWork(currentTask); // 执行任务
taskQueue.shift(); // 移除已完成的队列
} else {
// 执行任务回调
const result = currentTask.callback();
if (typeof result === 'function') {
currentTask.callback = result; // 更新回调(支持任务拆分)
} else {
taskQueue.shift(); // 移除已完成的队列
}
}
}
// 如果队列中仍有任务,重新调度
if (taskQueue.length > 0) {
schedulePerformWorkUntilIdle();
}
}
// 启动调度器(利用 requestIdleCallback 在空闲时执行)
function schedulePerformWorkUntilIdle() {
requestIdleCallback(workLoop, { timeout: 500 }); // 设置超时时间为 500ms
}
// 调度接口(对外暴露的 API)
function unstable_scheduleCallback(priorityLevel, callback, options = {}) {
const startTime = getCurrentTime(); // 获取当前时间戳
const timeout = options.timeout || (() => {
// 根据优先级设置默认超时时间
switch (priorityLevel) {
case ImmediatePriority:
return -1; // 立即执行(无超时)
case UserBlockingPriority:
return 250; // 250ms(动画帧级别)
case NormalPriority:
return 10000; // 10s(默认任务)
case LowPriority:
return 10000; // 10s(低优先级任务)
case IdlePriority:
return 100000000; // 100s(空闲任务)
}
})();
const expirationTime = startTime + timeout; // 计算截止时间
// 创建新任务对象
const newTask = {
callback, // 任务回调函数
priorityLevel, // 任务优先级
startTime, // 任务开始时间
expirationTime, // 任务截止时间
sortIndex: 0, // 排序索引(由 insertTask 计算)
};
insertTask(newTask); // 插入任务到队列
schedulePerformWorkUntilIdle(); // 启动调度器
}
// 导出接口(用于调试或扩展)
window.unstable_scheduleCallback = unstable_scheduleCallback;
window.ImmediatePriority = ImmediatePriority;
window.UserBlockingPriority = UserBlockingPriority;
window.NormalPriority = NormalPriority;
window.LowPriority = LowPriority;
window.IdlePriority = IdlePriority;
五、完整代码注释说明
insertTask()
:将新任务插入有序队列,按优先级和截止时间排序。workLoop()
:主工作循环,判断是否可以继续执行任务。shouldYieldToHost()
:判断当前是否应该让出主线程。schedulePerformWorkUntilIdle()
:使用requestIdleCallback
启动调度。unstable_scheduleCallback()
:对外暴露的调度接口,用于注册任务。
六、设计模式分析
设计模式 | 应用场景 |
---|---|
策略模式 | 不同优先级对应不同的调度策略(如 Immediate、Normal) |
观察者模式 | 任务完成后通知调度器继续执行 |
责任链模式 | 多个任务依次执行,形成调度链 |
状态机模式 | 任务具有不同的状态(等待、执行、完成) |
命令模式 | 将每个任务封装为可执行的回调对象 |
工厂模式 | 创建任务对象并插入队列 |
七、10大调度器(Scheduler)高频面试题
-
React 的 Scheduler 是做什么的?为什么需要它?
- 用于实现并发调度,支持中断和恢复任务,提升响应速度。
-
Scheduler 如何支持不同优先级的任务?
- 使用
LanePriority
表示优先级,并在调度时按优先级排序执行。
- 使用
-
什么是时间片调度(Time Slicing)?它是如何实现的?
- 每帧只执行一部分任务,剩余任务延迟执行,通过
requestIdleCallback
实现。
- 每帧只执行一部分任务,剩余任务延迟执行,通过
-
如何判断一个任务是否需要中断?
- 通过
deadline
判断是否已超时,若超时则中断当前任务。
- 通过
-
Scheduler 是如何利用浏览器的 requestIdleCallback 的?
- 在空闲时间执行任务,避免阻塞主线程。
-
如何理解 “callback 返回函数” 的续执行机制?
- 如果 callback 返回函数,表示该任务未完成,下次继续执行。
-
什么是任务的 expirationTime?它的作用是什么?
- 表示任务的截止时间,超过这个时间后即使低优先级也要执行。
-
Scheduler 是如何支持并发更新的?
- 通过多个任务并行执行、优先级排序、中断恢复机制实现。
-
useTransition 和 useDeferredValue 是如何影响 Scheduler 的?
- 降低某些更新的优先级,使其不打断用户交互。
-
Scheduler 是如何跨平台兼容的?
- 抽象 HostConfig 层,根据不同平台(Web、Native)实现底层调度逻辑。
八、总结
React 的 Scheduler 是其并发调度系统的核心模块,它不仅解决了传统同步更新带来的性能瓶颈,还为构建高性能、响应性强的现代 Web 应用提供了坚实基础。
通过本文的学习,你已经掌握了以下内容:
- Scheduler 的设计思想与历史演进
- LanePriority 的定义与映射机制
- Scheduler 的核心算法与流程
- 一个完整的简化版 Scheduler 实现
- 设计模式的应用分析
- 10道高频面试题解析