
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 排的是宏任务,所以它能排在一帧的绘制之后执行;微任务会在当前任务结束前清空,起不到让出主线程的作用。
「调度器保证某个延迟」。官方没有给出「多久一定执行」的承诺,这类数字都来自社区测量,随版本与设备变化。
分享文章
生成精美分享图或复制链接,与更多人分享本文。
继续阅读
换条路线
从其他文章中稳定抽取
最后更新于 ,距今已过 192 天
部分内容可能已过时