人人都会AI编程

5.3 React 调度系统(Scheduler)

更新时间:2026-07-10

为什么需要调度系统

在 React 16 之前,当组件状态更新时,React 会一口气执行完整个更新过程(包括 render 和 commit),期间一直占用主线程,如果组件树很大,就会导致长时间的 JavaScript 执行,阻塞浏览器的渲染和用户交互,表现为掉帧、点击无响应,也就是所谓的 “同步渲染卡顿”

React 16 引入 Fiber 架构后,render 阶段变得可以中断,但这还不够——我们需要一个 调度器 来决定“什么时候开始处理更新”、“以怎样的顺序处理不同优先级的更新”、“如何合理利用浏览器空闲时间”,从而在保持响应的同时高效地完成更新。

这就是 Scheduler,React 的独立调度包(scheduler),它负责管理所有并发更新的执行时机。

优先级机制:Lane 模型与优先级分类

调度系统最核心的能力就是给不同的更新赋予不同的优先级,高优先级的更新可以打断低优先级的更新。React 18 使用 Lane 模型 来管理优先级。

Lane 是什么
Lane 可以理解为一个“赛道”,不同的更新走不同的赛道,每个赛道对应一个 31 位的二进制位,优先级越高,位的位置越靠右(bit 值越小)。一条更新可能同时占用多个 lane(比如需要挂起、重试等)。

React 内部定义了多种优先级,常见的有:

| 优先级类型 | 触发场景 | 超时时间 | 对应事件 |
|------------|----------|----------|----------|
| 同步优先级 (SyncLane) | 需要立即同步执行的任务,例如 flushSync、React 18 之前的 setState 在原生事件中 | 立即执行 | DOM 事件(如 click) |
| 输入事件优先级 (InputContinuousLane) | 用户持续输入、拖拽、动画等 | 约 50ms | mousemovescroll |
| 默认优先级 (DefaultLane) | 一般的状态更新,如 setState | 约 5s | setTimeout、网络回调 |
| 空闲优先级 (IdleLane) | 无需立即展示的更新,如离屏内容 | 永不超时 | useTransition 包裹的更新 |

注意:React 的源码中优先级分为两大体系:事件优先级EventPriority)和 更新优先级Lane),两者通过“优先级等级”映射在一起。日常开发中我们只需知道对应用户交互的更新优先级较高即可。

Lane 的实际作用

  • 当你在用户输入的 onChange 中执行 setState,React 会将其标记为 InputContinuousLane,高优先级。
  • 当你用 startTransition 包裹一个状态更新,React 会降低它的优先级,变成 TransitionLane,使其可以被更高优先级的更新打断。
  • 多个更新会在调度器中进行合并或排序,优先执行高优先级的,低优先级的等待空闲时间。

任务调度与浏览器空闲时间利用

React 调度器的目标是在浏览器每帧(约 16ms,60fps)的剩余时间内执行更新任务,保证不掉帧。

调度实现原理
React 并没有直接使用 requestIdleCallback(因为它的兼容性和 50ms 的时间片太长),而是通过 MessageChannel 来模拟宏任务,结合 时间切片(Time Slicing) 机制:

  1. 任务分片:一个大的更新任务被拆分成多个小的工作单元(一个 Fiber 节点的处理约 5ms)。
  2. 时间预算:Scheduler 会在每个工作单元执行后检查是否还有剩余时间(每帧 5ms,给浏览器留下 11ms 渲染和通信)。如果时间不够,就把执行权交还给浏览器,等待下一个宏任务再继续。
  3. 中断与恢复:当浏览器空闲并再次调度时,React 从上次中断的 Fiber 节点继续工作。
  4. 任务队列:Scheduler 维护了多个优先级队列(unstable_scheduleCallback),高优先级任务(如用户点击)可以插入队列头部并中断当前低优先级任务。

开发者能感知到的效果

  • 在 React 18 的并发模式下,即使有一个耗时很长的列表渲染,也不会阻塞输入框的输入,因为高优先级的输入更新会打断列表渲染。
  • 通过 useTransitionuseDeferredValue,你可以主动将部分更新标记为低优先级,让 UI 对用户交互保持即时响应。
import { useTransition, useState } from 'react';

function SearchPage() {
  const [query, setQuery] = useState('');
  const [isPending, startTransition] = useTransition();
  const [results, setResults] = useState([]);

  function handleChange(e) {
    // 高优先级:立即更新输入框的值
    setQuery(e.target.value);
    // 低优先级:将过滤/渲染任务标记为 Transition
    startTransition(() => {
      setResults(filterHeavyData(e.target.value));
    });
  }

  return (
    <div>
      <input value={query} onChange={handleChange} />
      {isPending ? <Spinner /> : <ResultList data={results} />}
    </div>
  );
}

在这个例子中,用户输入的每一个字符都立即反映在输入框中(高优先级),但搜索结果列表的更新可能会被延迟(低优先级),甚至会因为新的输入到来而被取消,从而避免不必要的计算和渲染,保持界面流畅。

调度系统与 React 渲染阶段的关系

  • Render 阶段:可中断,由调度器控制执行。React 会在每个任务单元结束后检查是否需要让出主线程。
  • Commit 阶段:同步执行,不可中断。因为此时要操作真实 DOM,必须一气呵成以保证 UI 的一致性。

调度器只在 React 的并发模式下生效(React 18 默认开启)。它使得 React 从一个“尽力快速完成更新”的库,升级为一个“智能协调更新时机”的 UI 运行时,这就是并发渲染的基石。

小结

React 调度系统(Scheduler)利用 Lane 优先级模型 给更新划分等级,通过 时间切片浏览器空闲时间调度 实现了可中断的渲染,从而让应用在复杂交互下依然保持高响应性。作为开发者,我们通过 useTransitionuseDeferredValue 等 API 直接使用调度能力,书写出既快又流畅的用户体验。