React 源码系列:react 用到的 scheduler 库是何方神圣

1,143 阅读3分钟

「这是我参与11月更文挑战的第21天,活动详情查看:2021最后一次更文挑战

上一篇文章提到了 react 有 cancelCallbackscheduleCallbackscheduleSyncCallbackscheduleMicrotask 等函数,其中的 cancelCallbackscheduleCallback都是来自于一个叫scheduler 的库,今天我们一起来看看 scheduler 做了些什么?

 // packages/react-reconciler/src/Scheduler.js
 import * as Scheduler from 'scheduler';
 ​
 export const scheduleCallback = Scheduler.unstable_scheduleCallback;
 export const cancelCallback = Scheduler.unstable_cancelCallback;

scheduler 介绍

一句话来说就是 React 是用它来在浏览器环境进行协同调度的。 一开始我以为这是一个第三方库,后来发现它就在 react 仓库的 packages/scheduler 目录下。

它的index.js就一行 export 语句:export * from './src/forks/Scheduler'; 按图索骥来到Scheduler.js,约600行的代码,但先不从头开始看,先来看我关心的 unstable_scheduleCallbackunstable_cancelCallback

unstable_scheduleCallback

函数签名

 function unstable_scheduleCallback(priorityLevel, callback, options) {}

代码逻辑

  1. 获取当前时间 currentTime,加上延迟时间 options.delay,计算出开始时间startTime
  2. 根据priorityLevel 的取值得到不同的timeout
  3. 过期时间 expirationTime = startTime + timeout
  4. 创建一个新的任务
   var newTask = {
     id: taskIdCounter++,
     callback,
     priorityLevel,
     startTime,
     expirationTime,
     sortIndex: -1,
   };
  1. 如果 newTask 是延迟任务,即延迟时间不为 0,以startTime为排序索引(sortIndex),放入名为 timerQueue 的优先级队列中。
  2. 如果 taskQueue 为空,该任务是 timerQueue 中优先级最高的,检查 isHostTimeoutScheduled 标志,如果为 true, 则调用cancelHostTimeout 函数,否则标记 isHostTimeoutScheduled 为 true。检查完isHostTimeoutScheduled之后,调度一个 timeout。(本质上就是如果新任务优先级最高,则取消原本将要执行的任务,优先执行新任务)
 requestHostTimeout(handleTimeout, startTime - currentTime);
  1. 如果 newTask 不是延迟任务,则以过期时间为排序索引,放入任务队列 taskQueue
  2. 如果 isHostCallbackScheduled 为 false 且 isPerformingWork 为 false,标记 isHostCallbackScheduled 为 true,执行 requestHostCallback(flushWork);
  3. 最后返回 newTask

过完上面的代码逻辑,会产生新的疑问—— requestHostTimeoutcancelHostTimeout 分别做了什么?requestHostCallback 又做了什么?

requestHostTimeout 本质就是一个 setTimeout 定时器类型的宏任务调度。

 function requestHostTimeout(callback, ms) {
   taskTimeoutID = localSetTimeout(() => {
     callback(getCurrentTime());
   }, ms);
 }

这里的 localSetTimeout 就是 setTimeout 函数的别名,取别名是为了防止用户在后面重写了全局的 setTimeout 函数导致功能失效。

同理,localClearTimeout 是 clearTimeout 的别名。cancelHostTimeout 的作用就是调用了 localClearTimeout 并将 taskTimeoutID 重置为 -1.

至于 requestHostCallback,它会接受一个 callback 参数,赋值给全局变量 scheduledHostCallback,然后开启 callbackschedulePerformWorkUntilDeadline -> performWorkUntilDeadline -> scheduledHostCallback ->schedulePerformWorkUntilDeadline 消息大循环,直到 scheduledHostCallback 调用的返回值为 false,将scheduledHostCallback 重置为 null。

前面我们看到被传入 `requestHostCallback 的是 flushWork,后者的核心代码如下:

 ​
 function flushWork(hasTimeRemaining, initialTime) {
   isHostCallbackScheduled = false;
   if (isHostTimeoutScheduled) {
     // 我们已经调度了一个 timeout,但是它已经没用了,退出该 timeout
     isHostTimeoutScheduled = false;
     cancelHostTimeout();
   }
     
   isPerformingWork = true;
   const previousPriorityLevel = currentPriorityLevel;
   try {
      return workLoop(hasTimeRemaining, initialTime);
   } finally {
     currentTask = null;
     currentPriorityLevel = previousPriorityLevel;
     isPerformingWork = false;
   }
 }

unstable_cancelCallback

内容很简单,就是将入参的 callback字段置空

 function unstable_cancelCallback(task) {
   if (enableProfiling) {
     // ...
   }
 
   // Null out the callback to indicate the task has been canceled. (Can't
   // remove from the queue because you can't remove arbitrary nodes from an
   // array based heap, only the first one.)
   task.callback = null;
 }

下期预告

  1. flushWork 中的 workLoop 做了什么?
  2. 这个库实现任务调度的整体原理是什么?