React调度系统Scheduler深度解析
# React 调度系统 Scheduler 深度解析
在 React 中,**调度系统(Scheduler)** 是负责管理任务优先级、拆分工作并在合适时机执行的底层模块。它让 React 能够在保持界面流畅的同时,以合理的优先级顺序执行各种更新任务。本文将从 Scheduler 的核心概念、源码结构、任务优先级、工作循环(Work Loop)及常用 API 等方面进行深度解析,结合代码示例与 ASCII 图解,帮你理清它的实现逻辑与使用方式。
---
## 目录
1. [前言:为何需要调度系统](#前言为何需要调度系统)
2. [Scheduler 核心概念](#scheduler-核心概念)
1. [任务优先级(Priority Levels)](#任务优先级priority-levels)
2. [时间切片与让出(Time Slicing & Yielding)](#时间切片与让出time-slicing--yielding)
3. [Callback 与 Task](#callback-与-task)
3. [Scheduler API 及典型代码示例](#scheduler-api-及典型代码示例)
1. [安装与导入](#安装与导入)
2. [调度一个低优先级任务](#调度一个低优先级任务)
3. [判断是否应该让出(`shouldYieldToHost`)](#判断是否应该让出shouldyieldtohost)
4. [Scheduler 源码结构与关键模块](#scheduler-源码结构与关键模块)
1. [`Scheduler.js` 主入口](#schedulersjs-主入口)
2. [`SchedulerHostConfig`](#schedulerhostconfig)
3. [优先级枚举与内部实现](#优先级枚举与内部实现)
4. [任务队列与环形链表](#任务队列与环形链表)
5. [工作循环(Work Loop)深度剖析](#工作循环work-loop深度剖析)
1. [同步模式 Work Loop](#同步模式-work-loop)
2. [并发模式 Work Loop](#并发模式-work-loop)
3. [`performWorkUntilDeadline` 如何中断与恢复](#performworkuntildeadline-如何中断与恢复)
6. [任务优先级调度流程图解](#任务优先级调度流程图解)
7. [基于 Scheduler 实现简易任务调度示例](#基于-scheduler-实现简易任务调度示例)
8. [常见误区与优化建议](#常见误区与优化建议)
9. [总结与学习建议](#总结与学习建议)
---
## 前言:为何需要调度系统
在传统的单线程 JavaScript 环境中,UI 渲染和业务逻辑都在同一个线程上执行。如果有一个耗时操作(如大规模数据处理、复杂的布局计算等)直接在主线程执行,就会导致界面卡顿、动画丢帧,造成用户体验下降。为了解决这一问题,React 引入了 **调度系统(Scheduler)**,负责将大任务拆分为若干小“工作单元(work unit)”,并结合浏览器空闲时间片(`requestIdleCallback` 或轮询 `postMessage`)动态切换执行。
Scheduler 的核心价值在于:
- **控制更新优先级**:不同类型的操作(用户交互、动画、数据更新)具有不同紧急程度,Scheduler 允许我们为任务标记优先级,从而先执行高优先级任务,后执行低优先级任务。
- **分片渲染(Time Slicing)**:将一个大任务拆成多个小任务,保证每段执行时间不会超过阈值,让出主线程给浏览器进行渲染与用户交互。
- **可中断与恢复**:在执行过程中,若遇到更高优先级任务到来,可暂停当前任务,待高优先级任务完成后再恢复执行,提高响应速度。
接下来我们先从核心概念讲起。
---
## Scheduler 核心概念
### 任务优先级(Priority Levels)
Scheduler 把任务优先级分为五档,源码中用常量说明:
```js
export const ImmediatePriority = 1;
export const UserBlockingPriority = 2;
export const NormalPriority = 3;
export const LowPriority = 4;
export const IdlePriority = 5;
ImmediatePriority(同步任务)
- 优先级最高,用于需要立即执行的任务,例如 React 的同步更新(事件处理函数中的
setState
)。 - 这类任务会同步完成,不会中断。
- 优先级最高,用于需要立即执行的任务,例如 React 的同步更新(事件处理函数中的
UserBlockingPriority(用户阻塞任务)
- 比如用户点击、输入等离散事件,需要尽快响应。
- 在并发模式下,这类任务会被优先安排。
NormalPriority(普通任务)
- 普通更新(如异步数据更新)通常属于此类。
- 可以被更高优先级任务打断。
LowPriority(低优先级任务)
- 不急要的后台更新,例如预取数据、日志上报等。
- 在主线程空闲时才会执行。
IdlePriority(空闲任务)
- 最低优先级,只有在页面长时间空闲(没有更高优先级任务)时才会执行。
- 适合做缓存清理、统计埋点等“可延后”工作。
时间切片与让出(Time Slicing & Yielding)
浏览器每一帧大约有 16ms 的时间可用,当任务执行超过阈值(通常 5ms 左右)后,应让出主线程,让浏览器完成渲染、处理用户输入,再在下一帧继续执行剩余任务。Scheduler 借助以下原语实现这一逻辑:
requestIdleCallback
/cancelIdleCallback
- 在主线程空闲时执行回调,参数中包含
timeRemaining()
来判断剩余时间。 - 若不支持
requestIdleCallback
(如部分浏览器),Scheduler 会使用postMessage
或setTimeout
模拟实现。
- 在主线程空闲时执行回调,参数中包含
shouldYieldToHost()
/unstable_shouldYield()
- 调用以检查当前帧是否剩余足够时间,若不足则应中断当前任务并安排下次继续。
时间阈值(deadline)
- 默认为 \~5ms,每次运行 Work Loop 时会计算开始时间与当前时间差,若超过阈值则暂停。
Callback 与 Task
Scheduler 内部维护一条优先级任务队列,每个任务用一个 CallbackNode
表示。一个任务(callback)至少包含以下属性:
type CallbackNode = {
callback: (expirationTime: number) => any,
priorityLevel: number,
expirationTime: number,
next: CallbackNode | null,
previous: CallbackNode | null,
};
callback
:实际要执行的函数,一旦调度到 CPU 空闲就会调用它。priorityLevel
:任务的优先级,决定它在队列中的排序。expirationTime
:任务的过期时间,若到期仍未执行,则应立即调度执行。next
/previous
:形成一个环形双向链表,用以管理任务队列。
当我们调用 unstable_scheduleCallback(priorityLevel, callback, options)
时:
- 创建一个新的
CallbackNode
,设置好priorityLevel
与expirationTime
(默认过期时间依赖优先级)。 - 将该节点插入到已有任务队列的合适位置,确保链表按优先级与过期时间排序。
- 如果队列中不存在正在执行的工作循环(work loop),则调用
requestIdleCallback
提交对performWorkUntilDeadline
的调度。
Scheduler API 及典型代码示例
安装与导入
如果你使用的是 React 17+,Scheduler 已包含在 React 包中;也可单独安装使用最新版本:
# React 内置(React 17 以后)
import {
unstable_scheduleCallback as scheduleCallback,
unstable_UserBlockingPriority as UserBlockingPriority,
unstable_NormalPriority as NormalPriority,
unstable_shouldYield as shouldYield,
} from 'scheduler';
# 或单独安装
npm install scheduler
# 然后导入同上
调度一个低优先级任务
下面示例演示如何调度一个低优先级任务,并在可用时逐步执行计算密集型操作,而不中断用户交互体验。
import React, { useState, useRef } from 'react';
import { Button, View } from 'react-native';
import {
unstable_scheduleCallback as scheduleCallback,
unstable_LowPriority as LowPriority,
unstable_shouldYield as shouldYield,
} from 'scheduler';
export default function HeavyComputationDemo() {
const [result, setResult] = useState(null);
const workLoopId = useRef(null);
// 一个模拟“重度计算”的大循环
const heavyComputation = () => {
let i = 0;
const max = 1e8;
let sum = 0;
function work() {
// 分片执行:每次计算 10000 次后检查是否应让出
const chunkSize = 10000;
for (let c = 0; c < chunkSize && i < max; c++, i++) {
sum += Math.sqrt(i);
}
if (i < max && !shouldYield()) {
// 还没到最大,并且当前帧还有空闲时间,继续执行
workLoopId.current = scheduleCallback(LowPriority, work);
} else if (i < max) {
// 当前帧时间耗尽,下一帧继续
workLoopId.current = scheduleCallback(LowPriority, work);
} else {
// 计算完成,更新结果
setResult(sum);
}
}
work();
};
return (
<View style={{ padding: 20 }}>
<Button title="开始重度计算" onPress={heavyComputation} />
{result !== null && <Text>计算结果:{result}</Text>}
</View>
);
}
示例说明:
- 点击 “开始重度计算” 后,入口函数
heavyComputation
启动一个分片工作work
。 - 每次循环固定执行
chunkSize
次(如 10,000 次),然后用shouldYield()
判断当前帧是否剩余时间。 - 若时间未耗尽并且尚未完成所有循环,就通过
scheduleCallback(LowPriority, work)
安排下一批任务。 - 当循环完成后,将最终
sum
存入组件状态。此时即使有其他高优先级任务(如点击按钮、滚动),Scheduler 也会中断当前批次、先让高优先级任务执行。
判断是否应该让出(shouldYieldToHost
)
shouldYield
(alias unstable_shouldYield
)是 Scheduler 暴露给用户检查当前帧是否应当让出的 API,底层会调用 SchedulerHostConfig.shouldYieldToHost()
。在浏览器环境下,它会基于 requestIdleCallback
的 timeRemaining()
;在 React Native 环境下(或不支持 requestIdleCallback
),会使用 postMessage
垒式触发“微任务”并监测时间差。
简化版伪代码:
let frameDeadline = 0;
let isMessageLoopRunning = false;
// 当浏览器空闲时(requestIdleCallback)或 setTimeout 触发时:
function performWorkUntilDeadline(deadline) {
frameDeadline = deadline.timeRemaining() + getCurrentTime();
isMessageLoopRunning = true;
workLoopConcurrent();
isMessageLoopRunning = false;
// 如果未完成所有任务,再次调度 performWorkUntilDeadline
}
export function unstable_shouldYield() {
// 当前时间超过 deadline,就应该让出
return getCurrentTime() >= frameDeadline;
}
workLoopConcurrent
则会在每个单元执行后调用 unstable_shouldYield()
,若返回 true
,则中断循环并安排下一空闲时段继续。
Scheduler 源码结构与关键模块
下面让我们走近 Scheduler 的源码,剖析其目录结构与模块职责。
Scheduler.js
主入口
在 node_modules/scheduler/index.js
(或 React 内置 react/src/ReactSharedInternals/scheduler
)中,主要暴露的 API 包括:
export {
unstable_scheduleCallback as scheduleCallback,
unstable_cancelCallback as cancelCallback,
unstable_shouldYield as shouldYield,
unstable_runWithPriority as runWithPriority,
unstable_getCurrentPriorityLevel as getCurrentPriorityLevel,
unstable_requestPaint as requestPaint,
unstable_now as now,
unstable_ImmediatePriority as ImmediatePriority,
unstable_UserBlockingPriority as UserBlockingPriority,
unstable_NormalPriority as NormalPriority,
unstable_LowPriority as LowPriority,
unstable_IdlePriority as IdlePriority,
} from './scheduler';
这些函数和常量封装在 scheduler/src/forks/Scheduler.js
(或同名文件)中。核心逻辑主要分为以下几类文件:
Scheduler.js
:高层 API 定义,调用底层实现。SchedulerHostConfig.*
:不同环境(浏览器、React Native、Node.js)的适配配置。SchedulerImplementation.*
:核心算法实现,包括任务队列、调度逻辑、Work Loop 等。
SchedulerHostConfig
Scheduler 需要与宿主环境(Host)交互,比如获取当前时间、注册空闲回调、取消空闲回调、判断是否让出等。SchedulerHostConfig
定义了这些接口的默认实现,有多套实现:
- 浏览器环境:用
requestIdleCallback
、postMessage
、performance.now()
。 - React Native 环境:没有
requestIdleCallback
,使用setTimeout(..., 0)
或直接基于global.performance.now()
实现。 - Node.js 环境:用
setImmediate
、process.hrtime
实现高精度计时与空闲回调。
以浏览器为例,简化版实现:
// SchedulerHostConfig.browser.js
export const requestHostCallback = (cb) => {
requestIdleCallback(cb, { timeout: 1 });
};
export const cancelHostCallback = (cbID) => {
cancelIdleCallback(cbID);
};
export const shouldYieldToHost = () => {
// 当前时间超过预设 deadline
return getCurrentTime() >= frameDeadline;
};
export const now = () => {
return performance.now();
};
在 React Native 中,这些函数会映射到 setTimeout
、clearTimeout
、global.performance.now()
等实现。
优先级枚举与内部实现
Scheduler 通过一个名为 PriorityLevel
的枚举管理优先级,并根据优先级划分“过期时间”。核心常量如下:
export const ImmediatePriority = 1;
export const UserBlockingPriority = 2;
export const NormalPriority = 3;
export const LowPriority = 4;
export const IdlePriority = 5;
// 对应的过期延迟(毫秒)
const IMMEDIATE_PRIORITY_TIMEOUT = -1;
const USER_BLOCKING_PRIORITY_TIMEOUT = 250;
const NORMAL_PRIORITY_TIMEOUT = 5000;
const LOW_PRIORITY_TIMEOUT = 10000;
const IDLE_PRIORITY_TIMEOUT = maxSigned31BitInt; // Infinity
当调用 scheduleCallback(priorityLevel, callback)
时,会计算该任务的 过期时间:
const currentTime = now();
let timeout;
switch (priorityLevel) {
case ImmediatePriority:
timeout = IMMEDIATE_PRIORITY_TIMEOUT;
break;
case UserBlockingPriority:
timeout = USER_BLOCKING_PRIORITY_TIMEOUT;
break;
case NormalPriority:
timeout = NORMAL_PRIORITY_TIMEOUT;
break;
case LowPriority:
timeout = LOW_PRIORITY_TIMEOUT;
break;
case IdlePriority:
timeout = IDLE_PRIORITY_TIMEOUT;
break;
}
const expirationTime = currentTime + timeout;
然后将任务按 (expirationTime, priorityLevel)
排序后插入环形链表,这样可以保证:
- 过期时间更早的任务优先调度。
- 同过期时间时,优先级更高的任务先执行。
任务队列与环形链表
Scheduler 用一个双向环形链表维护所有待执行任务。伪代码如下:
let firstTask = null; // 指向链表中的第一个节点
let lastTask = null;
// 新增任务
function scheduleCallback(priorityLevel, callback) {
const currentTime = now();
const expirationTime = currentTime + getTimeoutForPriority(priorityLevel);
const newTask = {
callback,
priorityLevel,
expirationTime,
next: null,
previous: null,
};
if (firstTask === null) {
firstTask = lastTask = newTask;
newTask.next = newTask.previous = newTask;
} else {
// 插入到链表尾部(简单示例,不按过期时间排序)
lastTask.next = newTask;
newTask.previous = lastTask;
newTask.next = firstTask;
firstTask.previous = newTask;
lastTask = newTask;
}
// 如果当前没有调度回调,就安排 performWorkUntilDeadline
if (!isSchedulerCallbackScheduled) {
isSchedulerCallbackScheduled = true;
requestHostCallback(performWorkUntilDeadline);
}
return newTask; // 可用于取消
}
在生产环境中,Scheduler 会在插入时按过期时间与优先级排序,以保证最紧急的任务先执行。
工作循环(Work Loop)深度剖析
调度器主要有两种运行模式:同步模式(Sync Work Loop)与并发模式(Concurrent Work Loop)。它们都基于“拆分任务为小块、轮询执行并在合适时机让出”这一思路,但并发模式更注重中断与恢复。
同步模式 Work Loop
当调度同步任务(ImmediatePriority
)时,Scheduler 不会分片中断,而是一次性执行完所有队列中同一优先级的任务。简化版伪码:
function workLoopSync() {
while (firstTask !== null) {
const currentTask = firstTask;
// 先移除该任务
removeTaskFromList(currentTask);
// 执行任务
currentTask.callback(currentTask.expirationTime);
// 如果 callback 返回了一个新的 callback(未完成),则重新调度
if (typeof currentTask.callback === 'function') {
scheduleCallback(currentTask.priorityLevel, currentTask.callback);
}
}
isSchedulerCallbackScheduled = false;
}
- 由于同步任务优先级最高,不会调用
shouldYield
,只要队列中还有任务就一直执行。 - 如果在任务执行过程中调用了
scheduleCallback(UserBlockingPriority, someTask)
,也会插入队列,待当前同步循环结束后再执行。
并发模式 Work Loop
并发模式下,Scheduler 会定期调用 unstable_shouldYield()
判断当前帧时间是否耗尽,从而中断循环并安排下一空闲周期继续。主要伪码如下:
let currentDeadline = 0;
function performWorkUntilDeadline(deadline) {
currentDeadline = deadline.timeRemaining() + now();
isSchedulerCallbackScheduled = false;
workLoopConcurrent();
}
function workLoopConcurrent() {
while (firstTask !== null) {
// 1. 如果任务已过期,则同步立即执行
const currentTime = now();
const currentTask = firstTask;
if (currentTask.expirationTime <= currentTime) {
// 过期任务同步执行
removeTaskFromList(currentTask);
currentTask.callback(currentTask.expirationTime);
continue;
}
// 2. 非过期任务,根据优先级执行一个单元
if (shouldYield()) {
// 时间片用尽,安排下一轮继续
scheduleHostCallback();
return;
} else {
// 尚有时间片,执行任务
removeTaskFromList(currentTask);
const continuationCallback = currentTask.callback(currentTask.expirationTime);
if (typeof continuationCallback === 'function') {
// 如果任务没有完成,返回一个 continuation callback,重新插入队列
scheduleCallback(currentTask.priorityLevel, continuationCallback);
}
}
}
}
关键点:
- 过期任务立即执行:若
expirationTime <= now()
,无论当前帧是否剩余时间,都同步执行。 - 判断是否让出:在执行非过期任务前,调用
shouldYield()
。若返回true
,表明当前帧剩余时间不足,需暂时让出主线程,安排performWorkUntilDeadline
在下一空闲时段再次执行。 - 任务拆分与继续:如果某个任务内部意识到自己尚未完成(例如 React 的 Fiber 单元),会返回一个“后续回调”(continuation callback),由调度器重新插入队列,下一轮继续执行。
performWorkUntilDeadline
如何中断与恢复
在浏览器中,performWorkUntilDeadline
由 requestIdleCallback
调用,并传入一个 deadline
对象,包含 deadline.timeRemaining()
。示意流程如下:
┌──────────────────────────────────────────────────────────────────┐
│ 浏览器空闲 → requestIdleCallback(performWork) │
└──────────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────┐
│ performWork(deadline): │
│ currentDeadline = now() + deadline.timeRemaining() │
│ workLoopConcurrent() │
│ if (firstTask !== null) { scheduleIdleCallback(performWork) } │
└──────────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────┐
│ workLoopConcurrent: │
│ while (firstTask) { │
│ if (task.expirationTime <= now()) { // 过期,立即执行 } │
│ else if (shouldYield()) { // 时间片用尽,停止循环 } │
│ scheduleIdleCallback(performWork); return; │
│ } else { // 执行一个工作单元 } │
│ runTaskUnit(); │
│ } │
│ } │
│ // 队列空或执行完成,不再调度 │
└──────────────────────────────────────────────────────────────────┘
- 中断条件:
shouldYield()
为true
。 - 恢复时机:任务尚未完成时,
workLoopConcurrent
内部主动调用scheduleHostCallback
(即requestIdleCallback
),把剩余任务延后到下一空闲周期执行。
任务优先级调度流程图解
为了更直观地理解 Scheduler 的任务调度流程,下面用 ASCII 图示分别说明普通任务(未过期)与过期任务的处理逻辑。
┌────────────────────────────────────────────────────────────┐
│ 调度者视角:scheduleCallback │
│ │
│ 1. 调用 scheduleCallback(priority, callback) │
│ 2. 计算 expirationTime = now() + timeoutForPriority(priority)│
│ 3. 将新任务插入环形链表,按 expirationTime 排序 │
│ 4. 如果没有 pendingCallback ,则调用 requestIdleCallback │
└────────────────────────────────────────────────────────────┘
↓ ↑
↓ 下一空闲周期 │
↓ │
┌────────────────────────────────────────────────────────────┐
│ 浏览器空闲 → 调用 performWorkUntilDeadline │
└────────────────────────────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────────┐
│ workLoopConcurrent 开始执行 │
│ │
│ while (firstTask) { │
│ currentTask = firstTask │
│ if (currentTask.expirationTime <= now()) { │
│ // 过期任务:立即执行,无需检查 shouldYield() │
│ removeTask(currentTask) │
│ currentTask.callback(currentTask.expirationTime) │
│ continue │
│ } │
│ if (shouldYield()) { // 时间片用尽 │
│ scheduleIdleCallback(performWorkUntilDeadline) │
│ return │
│ } │
│ // 尚有时间片:执行一个工作单元 │
│ removeTask(currentTask) │
│ contCallback = currentTask.callback(currentTask.expirationTime) │
│ if (typeof contCallback === 'function') { │
│ scheduleCallback(currentTask.priorityLevel, contCallback) │
│ } │
│ } │
│ │
│ // 队列空,停止调度 │
└────────────────────────────────────────────────────────────┘
- 上图展示了当
performWorkUntilDeadline
被调用后的内部逻辑。 - 过期任务会绕过
shouldYield()
检查,即使帧时间不足也要立即执行,以避免实时交互死锁。 - 非过期任务先检查
shouldYield()
,若时间片不足,则暂停当前循环并安排下一帧继续。
基于 Scheduler 实现简易任务调度示例
下面再给出一个完整的小示例,将多个不同优先级的任务添加到队列,并观察其执行顺序。
import React, { useEffect } from 'react';
import { View, Text } from 'react-native';
import {
unstable_scheduleCallback as scheduleCallback,
unstable_NormalPriority as NormalPriority,
unstable_UserBlockingPriority as UserBlockingPriority,
unstable_LowPriority as LowPriority,
unstable_IdlePriority as IdlePriority,
} from 'scheduler';
export default function SchedulerDemo() {
useEffect(() => {
console.log('当前时间:', Date.now());
// 用户阻塞任务:优先级较高
scheduleCallback(UserBlockingPriority, () => {
console.log('1. 用户阻塞任务执行(优先级 2)');
});
// 普通任务:优先级 3
scheduleCallback(NormalPriority, () => {
console.log('2. 普通任务执行(优先级 3)');
});
// 低优先级任务:优先级 4
scheduleCallback(LowPriority, () => {
console.log('3. 低优先级任务执行(优先级 4)');
});
// 空闲任务:优先级 5
scheduleCallback(IdlePriority, () => {
console.log('4. 空闲任务执行(优先级 5)');
});
// 同步任务:优先级 1,会立即执行
scheduleCallback(ImmediatePriority, () => {
console.log('0. 同步任务执行(优先级 1)');
});
}, []);
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text>查看控制台输出,观察执行顺序</Text>
</View>
);
}
运行结果(Console)
当前时间:1620000000000
0. 同步任务执行(优先级 1)
1. 用户阻塞任务执行(优先级 2)
2. 普通任务执行(优先级 3)
3. 低优先级任务执行(优先级 4)
4. 空闲任务执行(优先级 5)
- 同步任务(ImmediatePriority) 最先执行,不经过时间切片检查。
- 随后
UserBlockingPriority
任务、NormalPriority
任务、LowPriority
任务、IdlePriority
任务按优先级依次执行。 - 如果其中某个任务内部耗时较长,则可能会被后续更高优先级任务打断(如果它们尚未过期且到来)。
常见误区与优化建议
误区:
unstable_scheduleCallback
会立即执行- 只有
ImmediatePriority
任务会同步执行;其余任务都需要等待浏览器空闲或下一帧时间片才会运行。
- 只有
误区:
shouldYield
返回true
即不执行任何任务shouldYield
只是表明当前帧剩余时间不足,如果任务已过期(expirationTime <= now()
),仍会立即执行,跳过让出逻辑。
优化:合理拆分任务粒度
- 当一个任务逻辑十分庞大(如大规模循环、复杂计算)时,应主动拆成多个子任务,避免一次执行耗时过长。
- 使用
unstable_scheduleCallback
在循环内部分片,确保shouldYield()
能及时生效。
优先级选取示例
- 用户输入、点击事件 等敏感交互要使用
UserBlockingPriority
,避免输入迟滞。 - 数据轮询、预加载 可使用
LowPriority
,不抢占重要更新的执行。 - 日志、分析、缓存清理 等极低优先任务,用
IdlePriority
,只有在完全空闲时才执行。
- 用户输入、点击事件 等敏感交互要使用
结合 React Concurrent Mode
- 在 React 18+ 并发模式下,Scheduler 与 React Fiber 调度紧密结合,
startTransition
会将更新标记为“可中断”的低优先级更新(通常映射到NormalPriority
)。 - 不要在
render
中做过度阻塞主线程的操作,否则会影响并发更新的效果。
- 在 React 18+ 并发模式下,Scheduler 与 React Fiber 调度紧密结合,
总结与学习建议
本文从以下几个方面深度解析了 React 的调度系统 Scheduler:
- 核心概念:任务优先级、时间切片、让出逻辑。
- 主要 API:
unstable_scheduleCallback
、unstable_shouldYield
、Priority Levels
等。 - 源码架构:HostConfig、内部环形链表、过期时间排序、工作循环(同步/并发模式)。
- 典型示例:如何调度一个耗时计算并结合
shouldYield
分片执行;多优先级任务执行顺序;自定义任务调度示例。 - 常见误区与优化建议:正确理解让出与过期任务、拆分任务粒度、结合 React 并发模式等。
要进一步掌握 Scheduler,推荐以下学习路径:
阅读官方源码
- 仔细阅读
node_modules/scheduler/src/
中的Scheduler.js
、SchedulerHostConfig*.js
、SchedulerImplementation.js
文件。 - 理解各个模块之间如何协作:HostConfig 提供环境 API、Implementation 负责队列与执行、Scheduler.js 暴露给用户的接口。
- 仔细阅读
调试与实验
- 在浏览器或 React Native 环境下,多写几个示例,观察不同优先级任务互相打断的行为。
- 在 Chrome DevTools 或 React Native Debugger 中使用 Timeline/Profiler 模式,查看帧率与任务执行切片情况。
结合 React Fiber 调度
- 阅读 React 源码中的
ReactFiberWorkLoop.js
,了解 React 如何调用 Scheduler 来安排组件更新。 - 尝试在自定义 Hook 或组件中使用
startTransition
标记“可延迟更新”,观察界面响应变化。
- 阅读 React 源码中的
关注社区 RFC
- React 团队会在 GitHub 上发布 Scheduler 相关的 RFC(例如“支持优先级更细分”或“改进时间切片”),可定期跟踪。
- 参与社区讨论,有助于更快掌握新特性与最佳实践。
通过本文以及后续持续的源码阅读与实验,你将具备深入理解并高效使用 React 调度系统的能力,并能在复杂应用中通过合理安排任务优先级来优化性能与用户体验。
评论已关闭