不用一行代碼,搞懂 React 調度器原理
大家好,我卡頌。
Scheduler(調度器)[1] 是React重要的組成部分。
同時,他也是個獨立的包,任何**「連續、可中斷」**的流程都可以用Scheduler來調度,比如:
const work = {count: 100};
function doWork(work) {
work.count--;
console.log('do work!')
}
work滿足兩個條件:
-
工作是連續的。一共需要執行 100 次,每次執行時調用
doWork -
工作是可中斷的。中斷恢復後,接着中斷前的
work.count繼續執行就行
滿足這兩個條件的工作都可以用Scheduler來調度。
調度後,Scheduler內部會生成對應task,並在正確的時機執行task.callback:
const task1 = {
// 過期時間 等於 當前時間 + 優先級對應時間
expirationTime: currentTime + priority,
callback: doWork.bind(null, work)
}
本文會講解Scheduler的實現原理。知道你不喜歡看大段的代碼,所以本文沒有一行代碼。文末有Scheduler的源碼地址,感興趣的話可以去看看。
開整~
工作流程概覽
Scheduler的工作原理如下圖,接下來會詳細解釋:
在Scheduler中有兩個容易混淆的概念:
- delay
delay代表 「task 需要延遲執行的時間」 。配置了delay的task會先進入timerQueue中。
當delay對應時間到期後,該task會轉移到taskQueue中。
- expirationTime
expirationTime代表 「task 的過期時間」 。
不是所有task都會配置delay,沒有配置delay的task會直接進入taskQueue。這就導致taskQueue中可能存在多個task。
如何決定哪個task.callback先執行呢?Scheduler根據task.expirationTime作爲排序依據,值越小優先級越高。
如果task.expirationTime小於當前時間,不僅優先級最高,而且task.callback的執行不會被中斷。
總結一下task的幾種情況:
-
配置
delay且delay未到期:task一定不會執行 -
配置
delay且到期,或者未配置delay的task,同時task.expirationTime未到期:根據task.expirationTime排序後,按順序執行 -
task.expirationTime到期的task:優先級最高,且同步、不可中斷
工作流程詳解
將流程概覽圖替換爲Scheduler中具體方法後,如下:
完整工作流程如下:
- 執行
Scheduler.scheduleCallback生成task
根據 「是否傳遞 delay 參數」 ,生成的task會進入timerQueue或taskQueue。
- 當
timerQueue中第一個task延遲的時間到期後,執行advanceTimers將 「到期的 task」 從timerQueue中移到taskQueue中
其中,timerQueue、taskQueue的數據結構爲小頂堆實現的優先級隊列。
- 接下來,執行
requestHostCallback方法,他會在新的宏任務中執行workLoop方法
「在宏任務中執行回調」 的方法很多,Scheduler在瀏覽器環境默認使用MessageChannel實現。
如果不支持MessageChannel,會降級到setTimeout。Node或老版IE下會使用setImmediate。
workLoop方法會循環消費taskQueue中的task(即執行task.callback),直到滿足如下條件之一,中斷循環:
-
taskQueue中不存在task -
時間切片用盡
- 循環中斷後,如果
taskQueue不爲空,則進入步驟 3。如果timerQueue不爲空,則進入步驟 2
總結
總結一下,Scheduler的完整執行流程包括兩個循環:
-
taskQueue的生產(從timerQueue中移入或執行scheduleCallback生成)到消費的過程(即圖中灰色部分),這是個異步循環 -
taskQueue的具體消費過程(即workLoop方法的執行),這是個同步循環
如果你想了解 「React 中如何使用 Scheduler」 ,可以參考 100 行代碼實現 React 核心調度功能
參考資料
[1] Scheduler(調度器): https://github.com/facebook/react/blob/main/packages/scheduler/src/forks/Scheduler.js
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/ELzl08gB1iAs0hdEorxURw