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