100行代碼實現(xiàn)React核心調(diào)度功能

源?/?? ? ? ??文/?
想必大家都知道React有一套基于Fiber架構(gòu)的調(diào)度系統(tǒng)。這套調(diào)度系統(tǒng)的基本功能包括:
更新有不同優(yōu)先級
一次更新可能涉及多個組件的
render,這些render可能分配到多個宏任務(wù)中執(zhí)行(即時間切片)高優(yōu)先級更新會打斷進(jìn)行中的低優(yōu)先級更新
本文會用100行代碼實現(xiàn)這套調(diào)度系統(tǒng),讓你快速了解React的調(diào)度原理。
我知道你不喜歡看大段的代碼,所以本文會以圖+代碼片段的形式講解。
文末有完整的在線Demo,你可以自己上手玩玩。
開整!

準(zhǔn)備工作
我們用work這一數(shù)據(jù)結(jié)構(gòu)代表一份工作,work.count代表這份工作要重復(fù)做某件事的次數(shù)。
在Demo中要重復(fù)做的事是“執(zhí)行insertItem方法,向頁面插入”:
const?insertItem?=?(content:?string)?=>?{
??const?ele?=?document.createElement('span');
??ele.innerText?=?`${content}`;
??contentBox.appendChild(ele);
};
所以,對于如下work:
const?work1?=?{
??count:?100
}
代表:執(zhí)行100次insertItem向頁面插入100個。
work可以類比React的一次更新,work.count類比這次更新要render的組件數(shù)量。所以Demo是對React更新流程的類比
來實現(xiàn)第一版的調(diào)度系統(tǒng),流程如圖:

包括三步:
向
workList隊列(用于保存所有work)插入workschedule方法從workList中取出work,傳遞給performperform方法執(zhí)行完work的所有工作后重復(fù)步驟2
代碼如下:
//?保存所有work的隊列
const?workList:?work[]?=?[];
//?調(diào)度
function?schedule()?{
??//?從隊列尾取一個work
??const?curWork?=?workList.pop();
??
??if?(curWork)?{
????perform(curWork);
??}
}
//?執(zhí)行
function?perform(work:?Work)?{
??while?(work.count)?{
????work.count--;
????insertItem();
??}
??schedule();
}
為按鈕綁定點擊交互,最基本的調(diào)度系統(tǒng)就完成了:
button.onclick?=?()?=>?{
??workList.unshift({
????count:?100
??})
??schedule();
}
點擊button就能插入100個。
用
React類比就是:點擊button,觸發(fā)同步更新,100個組件render
接下來我們將其改造成異步的。
Scheduler
React內(nèi)部使用Scheduler完成異步調(diào)度。
Scheduler是獨立的包。所以可以用他改造我們的Demo。
Scheduler預(yù)置了5種優(yōu)先級,從上往下優(yōu)先級降低:
ImmediatePriority,最高的同步優(yōu)先級UserBlockingPriorityNormalPriorityLowPriorityIdlePriority,最低優(yōu)先級
scheduleCallback方法接收優(yōu)先級與回調(diào)函數(shù)fn,用于調(diào)度fn:
//?將回調(diào)函數(shù)fn以LowPriority優(yōu)先級調(diào)度
scheduleCallback(LowPriority,?fn)
在Scheduler內(nèi)部,執(zhí)行scheduleCallback后會生成task這一數(shù)據(jù)結(jié)構(gòu):
const?task1?=?{
??expiration:?startTime?+?timeout,
??callback:?fn
}
task1.expiration代表task1的過期時間,Scheduler會優(yōu)先執(zhí)行過期的task.callback。
expiration中startTime為當(dāng)前開始時間,不同優(yōu)先級的timeout不同。
比如,ImmediatePriority的timeout為-1,由于:
startTime?-?1?所以ImmediatePriority會立刻過期,callback立刻執(zhí)行。
而IdlePriority對應(yīng)timeout為1073741823(最大的31位帶符號整型),其callback需要非常長時間才會執(zhí)行。
callback會在新的宏任務(wù)中執(zhí)行,這就是Scheduler調(diào)度的原理。
用Scheduler改造Demo
改造后的流程如圖:

改造前,work直接從workList隊列尾取出:
//?改造前
const?curWork?=?workList.pop();
改造后,work可以擁有不同優(yōu)先級,通過priority字段表示。
比如,如下work代表「以NormalPriority優(yōu)先級插入100個」:
const?work1?=?{
??count:?100,
??priority:?NormalPriority
}
改造后每次都使用最高優(yōu)先級的work:
//?改造后
//?對workList排序后取priority值最小的(值越小,優(yōu)先級越高)
const?curWork?=?workList.sort((w1,?w2)?=>?{
???return?w1.priority?-?w2.priority;
})[0];
改造后流程的變化
由流程圖可知,Scheduler不再直接執(zhí)行perform,而是通過執(zhí)行scheduleCallback調(diào)度perform.bind(null, work)。
即,滿足一定條件的情況下,生成新task:
const?someTask?=?{
??callback:?perform.bind(null,?work),
??expiration:?xxx
}
同時,work的工作也是可中斷的。在改造前,perform會同步執(zhí)行完work中的所有工作:
while?(work.count)?{
??work.count--;
??insertItem();
}
改造后,work的執(zhí)行流程隨時可能中斷:
while?(!needYield()?&&?work.count)?{
??work.count--;
??insertItem();
}
needYield方法的實現(xiàn)(何時會中斷)請參考文末在線Demo
高優(yōu)先級打斷低優(yōu)先級的例子
舉例來看一個高優(yōu)先級打斷低優(yōu)先級的例子:
插入一個低優(yōu)先級 work,屬性如下
const?work1?=?{
??count:?100,
??priority:?LowPriority
}
經(jīng)歷 schedule(調(diào)度),perform(執(zhí)行),在執(zhí)行了80次工作時,突然插入一個高優(yōu)先級work,此時:
const?work1?=?{
??//?work1已經(jīng)執(zhí)行了80次工作,還差20次執(zhí)行完
??count:?20,
??priority:?LowPriority
}
//?新插入的高優(yōu)先級work
const?work2?=?{
??count:?100,
??priority:?ImmediatePriority
}
work1工作中斷,繼續(xù)schedule。由于work2優(yōu)先級更高,會進(jìn)入work2對應(yīng)perform,執(zhí)行100次工作work2執(zhí)行完后,繼續(xù)schedule,執(zhí)行work1剩余的20次工作
在這個例子中,我們需要區(qū)分2個「打斷」的概念:
在步驟3中,
work1執(zhí)行的工作被打斷。這是微觀角度的「打斷」由于
work1被打斷,所以繼續(xù)schedule。下一個執(zhí)行工作的是更高優(yōu)的work2。work2的到來導(dǎo)致work1被打斷,這是宏觀角度的「打斷」
之所以要區(qū)分「宏/微觀」,是因為「微觀的打斷」不一定意味著「宏觀的打斷」。
比如:work1由于時間切片用盡,被打斷。沒有其他更高優(yōu)的work與他競爭schedule的話,下一次perform還是work1。
這種情況下微觀下多次打斷,但是宏觀來看,還是同一個work在執(zhí)行。這就是「時間切片」的原理。
調(diào)度系統(tǒng)的實現(xiàn)原理
以下是調(diào)度系統(tǒng)的完整實現(xiàn)原理:

對照流程圖來看:

總結(jié)
本文是React調(diào)度系統(tǒng)的簡易實現(xiàn),主要包括兩個階段:
schedule
perform
如果你對代碼的具體實現(xiàn)感興趣,下面是完整Demo地址。
END


頂級程序員:topcoding
做最好的程序員社區(qū):Java后端開發(fā)、Python、大數(shù)據(jù)、AI
一鍵三連「分享」、「點贊」和「在看」
