779 字
4 分钟
React 的调度(Scheduler)

requestIdleCallback 是什么#

它是浏览器原生 API,作用是在主线程空闲的时候跑低优先级的任务。用法与规则可以直接查平台文档(MDN 的 Window.requestIdleCallback 与 IdleDeadline 两条),要点是:

const handle = requestIdleCallback(callback, { timeout: 1000 })
cancelIdleCallback(handle)

回调收到一个 IdleDeadline 对象,两个成员:timeRemaining() 返回当前帧还剩多少空闲时间,didTimeout 告诉你这次是不是因为超时被强制执行。

任务是自己在回调里分片的,因为一次空闲时间可能不够:

requestIdleCallback(deadline => {
while (deadline.timeRemaining() > 0 && hasWork()) {
doOneUnit()
}
if (hasWork()) {
requestIdleCallback(next) // 没做完,下一帧继续
}
})

timeout 的作用是防饿死:一直没有空闲时间时,浏览器会在超时后强制执行一次。

为什么 React 没用它#

社区给出的理由有三条:

  • 浏览器支持不全,Safari 长期没有实现这个 API。
  • 粒度偏粗。它是给日志上报这类后台任务设计的,回调被排到的间隔可能很长,撑不起界面更新要的节奏。
  • 各浏览器实现有差异,执行时机不好预期。

这三条是社区分析,不是官方说明。React 官方没有任何文档解释调度器的选型。

那 React 用什么让出主线程#

社区读源码得出的结论是 MessageChannel:向一个端口 postMessage,回调作为宏任务排到之后的事件循环里执行,当前这次工作就先让出去。

const channel = new MessageChannel()
channel.port1.onmessage = () => {
// 这里执行下一段任务
}
channel.port2.postMessage(null)

这条结论的性质要说清楚。React 仓库里有一个独立的 scheduler 包,源码公开,所以「它用 MessageChannel 让出主线程」是可核实的事实,不是猜测;但官方没有把它写成文档,也没有承诺不变。要确认当前版本的行为,看源码,别看二手文章。

同理,旧资料里那句「默认 5ms 时间片」也是社区描述,版本之间会变。要引这个数字,写出处与年代。

分片与「可中断」的关系#

把长任务拆成小段、每段之间让出主线程,好处是浏览器有机会处理输入与绘制。这解释了一个现象:一段很重的渲染不会让页面完全卡死。

但要注意这是实现层的解释。你在业务里能依赖的只有官方写明的行为:useTransition 的过渡更新可被打断,useDeferredValue 的后台渲染可被打断。写代码按这两条来,不要按时间片长度来。

三条容易写错的说法#

「React 用 requestIdleCallback 做时间切片」。它借鉴了这个思路,但没有用这个 API。

「MessageChannel 的回调是微任务」。postMessage 排的是宏任务,所以它能排在一帧的绘制之后执行;微任务会在当前任务结束前清空,起不到让出主线程的作用。

「调度器保证某个延迟」。官方没有给出「多久一定执行」的承诺,这类数字都来自社区测量,随版本与设备变化。

第 38 篇,共 39 篇本文属于系列

React 的调度(Scheduler)
https://www.choria.top/posts/react-schedule/
作者
乔瑞雅·克林威尔
发布于
2026-04-30
许可协议
CC BY-NC-SA 4.0

分享文章

生成精美分享图或复制链接,与更多人分享本文。

继续阅读

沿着主题读

基于共同的标签与分类

换条路线

从其他文章中稳定抽取

最后更新于 ,距今已过 192 天

部分内容可能已过时