不用一行程式碼,搞懂React排程器原理

卡頌發表於2021-12-27

大家好,我卡頌。

Scheduler(排程器)React重要的組成部分。

同時,他也是個獨立的包,任何連續、可中斷的流程都可以用Scheduler來排程,比如:

const work = {count: 100};

function doWork(work) {
  work.count--;
  console.log('do work!')
}

work滿足兩個條件:

  1. 工作是連續的。一共需要執行100次,每次執行時呼叫doWork
  2. 工作是可中斷的。中斷恢復後,接著中斷前的work.count繼續執行就行

滿足這兩個條件的工作都可以用Scheduler來排程。

排程後,Scheduler內部會生成對應task,並在正確的時機執行task.callback

const task1 = {
  // 過期時間 等於 當前時間 + 優先順序對應時間
  expirationTime: currentTime + priority,
  callback: doWork.bind(null, work)
}

本文會講解Scheduler的實現原理。知道你不喜歡看大段的程式碼,所以本文沒有一行程式碼。文末有Scheduler的原始碼地址,感興趣的話可以去看看。

開整~

工作流程概覽

Scheduler的工作原理如下圖,接下來會詳細解釋:

Scheduler中有兩個容易混淆的概念:

  1. delay

delay代表task需要延遲執行的時間。配置了delaytask會先進入timerQueue中。

delay對應時間到期後,該task會轉移到taskQueue中。

  1. expirationTime

expirationTime代表task的過期時間

不是所有task都會配置delay,沒有配置delaytask會直接進入taskQueue。這就導致taskQueue中可能存在多個task

如何決定哪個task.callback先執行呢?Scheduler根據task.expirationTime作為排序依據,值越小優先順序越高。

如果task.expirationTime小於當前時間,不僅優先順序最高,而且task.callback的執行不會被中斷。

總結一下task的幾種情況:

  1. 配置delaydelay未到期:task一定不會執行
  2. 配置delay且到期,或者未配置delaytask,同時task.expirationTime未到期:根據task.expirationTime排序後,按順序執行
  3. task.expirationTime到期的task:優先順序最高,且同步、不可中斷

工作流程詳解

將流程概覽圖替換為Scheduler中具體方法後,如下:

完整工作流程如下:

  1. 執行Scheduler.scheduleCallback生成task

根據是否傳遞delay引數,生成的task會進入timerQueuetaskQueue

  1. timerQueue中第一個task延遲的時間到期後,執行advanceTimers到期的tasktimerQueue中移到taskQueue

其中,timerQueuetaskQueue的資料結構為小頂堆實現的優先順序佇列

  1. 接下來,執行requestHostCallback方法,他會在新的巨集任務中執行workLoop方法

在巨集任務中執行回撥的方法很多,Scheduler在瀏覽器環境預設使用MessageChannel實現。

如果不支援MessageChannel,會降級到setTimeoutNode老版IE下會使用setImmediate

  1. workLoop方法會迴圈消費taskQueue中的task(即執行task.callback),直到滿足如下條件之一,中斷迴圈:
  • taskQueue中不存在task
  • 時間切片用盡
  1. 迴圈中斷後,如果taskQueue不為空,則進入步驟3。如果timerQueue不為空,則進入步驟2

總結

總結一下,Scheduler的完整執行流程包括兩個迴圈:

  1. taskQueue的生產(從timerQueue中移入或執行scheduleCallback生成)到消費的過程(即圖中灰色部分),這是個非同步迴圈
  2. taskQueue的具體消費過程(即workLoop方法的執行),這是個同步迴圈

如果你想了解React中如何使用Scheduler,可以參考100行程式碼實現React核心排程功能

歡迎加入人類高質量前端框架群,帶飛

相關文章