前言
在 React 18 的并发时代,Scheduler(调度器) 是实现非阻塞渲染的幕后英雄。它不只是 React 的一个模块,更是一个通用的、高性能的 JavaScript 任务调度库。它不仅让 React 任务可以“插队”,还让“长任务”不再阻塞浏览器 UI 渲染。
一、 核心概念:什么是 Scheduler?
Scheduler 是一个独立的包,它通过与 React 协调过程(Reconciliation)的紧密配合,实现了任务的可中断、可恢复、带优先级执行。
主要职责
- 优先级管理:根据任务紧急程度(如用户点击 vs 数据预取)安排执行顺序。
- 空闲时间利用:在浏览器每一帧的空闲时间处理不紧急的任务。
- 防止主线程阻塞:通过“时间片(Time Slicing)”机制,避免长任务导致页面假死。
二、 Scheduler 的完整调度链路
当一个 setState 触发后,Scheduler 内部会经历以下精密流程:
1. 任务创建与通知
当状态更新时,React 不会立即执行 Render。它首先会创建一个 Update对象来记录这次变更,这个对象中包含这次更新所需的全部信息,例如更新后的状态值,Lane车道模型分配的任务优先级.
2. 优先级排序与队列维护
-
任务优先级排序: 创建更新后,react会调用
scheduleUpdateOnFiber函数通知scheduler调度器有个一个新的任务需要调度,这时scheduler会对该任务确定一个优先级,以及过期时间(优先级越高,过期时间越短,表示越紧急) -
队列维护: 接着
scheduler会将该任务放入到循环调度中,scheduler对于任务循环调度在内部维护着两个队列,一个是立即执行队列taskQueue和延迟任务队列timeQueue,新任务会根据优先级进入到相应对列timerQueue(延时任务队列) :存放还未到开始时间的任务,按开始时间排序。taskQueue(立即任务队列) :存放已经就绪的任务,按过期时间排序。优先级越高,过期时间越短。
3. 时间片的开启:MessageChannel
将任务放入队列后,scheduler会调用requetHostCallback函数去请求浏览器在合适的时机去执行调度,该函数通过 MessageChannel对象中的port.postMessage 方法创建一个宏任务,浏览器在下一个宏任务时机触发 port.onmessage,并在这宏任务回调中启动 workLoop函数。
补充:Scheduler 会调用
requestHostCallback请求浏览器调度。它没有选择setTimeout,而是选择了MessageChannel。
为什么选 MessageChannel?
setTimeout(fn, 0)在浏览器中通常有 4ms 的最小延迟,且属于宏任务中执行时机较晚的。MessageChannel的port.postMessage产生的宏任务执行时机更早,且能更精准地在浏览器渲染帧之间切入。
4. 工作循环:workLoop
-
在宏任务回调中,调度器会进入
workLoop。它会调用performUnitOfWork函数循环地处理Fiber节点,对比新旧节点的props、state,并从队列中取出最紧急的任务交给 React 执行。 -
workLopp中会包含一个shouldYield函数中断检查函数,用于检查当前时间片是否耗尽以及是否有更高优先级的任务执行,如果有的话则会将主线程控制权交还给浏览器,以保证高优先级任务(如用户输入、动画)能及时响应。
5. 中断与恢复:shouldYield 的魔力
在 workLoop 执行过程中,每一项单元工作完成后,都会调用 shouldYield() 函数进行“路况检查”。
- 中断条件:如果当前时间片(通常为 5ms)耗尽,或者检测到有更紧急的用户交互(高优任务插队),
shouldYield返回true。 - 状态保存:此时 React 会记录当前
workInProgress树的位置,将控制权交还给浏览器。 - 任务恢复:Scheduler 会在下一个时间片通过
MessageChannel再次触发,从记录的位置继续执行,从而实现可恢复。
6. 任务插队
如果在执行一个低优先级任务时,有高优先级任务加入(如用户突然点击按钮),Scheduler会中断当前的低优任务并记录该位置,先执行高优任务。等高优任务完成后,再重新执行或继续之前的低优任务
三、 补充
- 执行时机对比:
MessageChannel确实在宏任务中非常快,但在某些极其特殊的情况下(如没有MessageChannel的旧环境),它会回退到setTimeout。 - 饥饿现象防止:如果一个低优先级任务一直被插队怎么办?Scheduler 通过过期时间解决。一旦任务过期,它会从
taskQueue中被提升为同步任务,强制执行。