帶你了解React中的優(yōu)先級
UI產(chǎn)生交互的根本原因是各種事件,這也就意味著事件與更新有著直接關(guān)系。不同事件產(chǎn)生的更新,它們的優(yōu)先級是有差異的,所以更新優(yōu)先級的根源在于事件的優(yōu)先級。一個更新的產(chǎn)生可直接導(dǎo)致React生成一個更新任務(wù),最終這個任務(wù)被Scheduler調(diào)度。
所以在React中,人為地將事件劃分了等級,最終目的是決定調(diào)度任務(wù)的輕重緩急,因此,React有一套從事件到調(diào)度的優(yōu)先級機(jī)制。
本文將圍繞事件優(yōu)先級、更新優(yōu)先級、任務(wù)優(yōu)先級、調(diào)度優(yōu)先級,重點梳理它們之間的轉(zhuǎn)化關(guān)系。
- 事件優(yōu)先級:按照用戶事件的交互緊急程度,劃分的優(yōu)先級
- 更新優(yōu)先級:事件導(dǎo)致React產(chǎn)生的更新對象(update)的優(yōu)先級(update.lane)
- 任務(wù)優(yōu)先級:產(chǎn)生更新對象之后,React去執(zhí)行一個更新任務(wù),這個任務(wù)所持有的優(yōu)先級
- 調(diào)度優(yōu)先級:Scheduler依據(jù)React更新任務(wù)生成一個調(diào)度任務(wù),這個調(diào)度任務(wù)所持有的優(yōu)先級
前三者屬于React的優(yōu)先級機(jī)制,第四個屬于Scheduler的優(yōu)先級機(jī)制,Scheduler內(nèi)部有自己的優(yōu)先級機(jī)制,雖然與React有所區(qū)別,但等級的劃分基本一致。下面我們從事件優(yōu)先級開始說起。
優(yōu)先級的起點:事件優(yōu)先級
React按照事件的緊急程度,把它們劃分成三個等級:
- 離散事件(DiscreteEvent):click、keydown、focusin等,這些事件的觸發(fā)不是連續(xù)的,優(yōu)先級為0。
- 用戶阻塞事件(UserBlockingEvent):drag、scroll、mouseover等,特點是連續(xù)觸發(fā),阻塞渲染,優(yōu)先級為1。
- 連續(xù)事件(ContinuousEvent):canplay、error、audio標(biāo)簽的timeupdate和canplay,優(yōu)先級最高,為2。
事件優(yōu)先級的Map
派發(fā)事件優(yōu)先級
事件優(yōu)先級是在注冊階段被確定的,在向root上注冊事件時,會根據(jù)事件的類別,創(chuàng)建不同優(yōu)先級的事件監(jiān)聽(listener),最終將它綁定到root上去。
- let listener = createEventListenerWrapperWithPriority(
- targetContainer,
- domEventName,
- eventSystemFlags,
- listenerPriority,
- );
createEventListenerWrapperWithPriority函數(shù)的名字已經(jīng)把它做的事情交代得八九不離十了。它會首先根據(jù)事件的名稱去找對應(yīng)的事件優(yōu)先級,然后依據(jù)優(yōu)先級返回不同的事件監(jiān)聽函數(shù)。
- export function createEventListenerWrapperWithPriority(
- targetContainer: EventTarget,
- domEventName: DOMEventName,
- eventSystemFlags: EventSystemFlags,
- priority?: EventPriority,
- ): Function {
- const eventPriority =
- priority === undefined
- ? getEventPriorityForPluginSystem(domEventName)
- : priority;
- let listenerWrapper;
- switch (eventPriority) {
- case DiscreteEvent:
- listenerWrapper = dispatchDiscreteEvent;
- break;
- case UserBlockingEvent:
- listenerWrapper = dispatchUserBlockingUpdate;
- break;
- case ContinuousEvent:
- default:
- listenerWrapper = dispatchEvent;
- break;
- }
- return listenerWrapper.bind(
- null,
- domEventName,
- eventSystemFlags,
- targetContainer,
- );
- }
最終綁定到root上的事件監(jiān)聽其實是dispatchDiscreteEvent、dispatchUserBlockingUpdate、dispatchEvent這三個中的一個。它們做的事情都是一樣的,以各自的事件優(yōu)先級去執(zhí)行真正的事件處理函數(shù)。
比如:dispatchDiscreteEvent和dispatchUserBlockingUpdate最終都會以UserBlockingEvent的事件級別去執(zhí)行事件處理函數(shù)。
以某種優(yōu)先級去執(zhí)行事件處理函數(shù)其實要借助Scheduler中提供的runWithPriority函數(shù)來實現(xiàn):
- function dispatchUserBlockingUpdate(
- domEventName,
- eventSystemFlags,
- container,
- nativeEvent,
- ) {
- ...
- runWithPriority(
- UserBlockingPriority,
- dispatchEvent.bind(
- null,
- domEventName,
- eventSystemFlags,
- container,
- nativeEvent,
- ),
- );
- ...
- }
這么做可以將事件優(yōu)先級記錄到Scheduler中,相當(dāng)于告訴Scheduler:你幫我記錄一下當(dāng)前事件派發(fā)的優(yōu)先級,等React那邊創(chuàng)建更新對象(即update)計算更新優(yōu)先級時直接從你這拿就好了。
- function unstable_runWithPriority(priorityLevel, eventHandler) {
- switch (priorityLevel) {
- case ImmediatePriority:
- case UserBlockingPriority:
- case NormalPriority:
- case LowPriority:
- case IdlePriority:
- break;
- default:
- priorityLevel = NormalPriority;
- }
- var previousPriorityLevel = currentPriorityLevel;
- // 記錄優(yōu)先級到Scheduler內(nèi)部的變量里
- currentPriorityLevel = priorityLevel;
- try {
- return eventHandler();
- } finally {
- currentPriorityLevel = previousPriorityLevel;
- }
- }
更新優(yōu)先級
以setState為例,事件的執(zhí)行會導(dǎo)致setState執(zhí)行,而setState本質(zhì)上是調(diào)用enqueueSetState,生成一個update對象,這時候會計算它的更新優(yōu)先級,即update.lane:
- const classComponentUpdater = {
- enqueueSetState(inst, payload, callback) {
- ...
- // 依據(jù)事件優(yōu)先級創(chuàng)建update的優(yōu)先級
- const lane = requestUpdateLane(fiber, suspenseConfig);
- const update = createUpdate(eventTime, lane, suspenseConfig);
- update.payload = payload;
- enqueueUpdate(fiber, update);
- // 開始調(diào)度
- scheduleUpdateOnFiber(fiber, lane, eventTime);
- ...
- },
- };
重點關(guān)注requestUpdateLane,它首先找出Scheduler中記錄的優(yōu)先級:schedulerPriority,然后計算更新優(yōu)先級:lane,具體的計算過程在findUpdateLane函數(shù)中,計算過程是一個從高到低依次占用空閑位的操作,具體的代碼在這里 ,這里就先不詳細(xì)展開。
- export function requestUpdateLane(
- fiber: Fiber,
- suspenseConfig: SuspenseConfig | null,
- ): Lane {
- ...
- // 根據(jù)記錄下的事件優(yōu)先級,獲取任務(wù)調(diào)度優(yōu)先級
- const schedulerPriority = getCurrentPriorityLevel();
- let lane;
- if (
- (executionContext & DiscreteEventContext) !== NoContext &&
- schedulerPriority === UserBlockingSchedulerPriority
- ) {
- // 如果事件優(yōu)先級是用戶阻塞級別,則直接用InputDiscreteLanePriority去計算更新優(yōu)先級
- lane = findUpdateLane(InputDiscreteLanePriority, currentEventWipLanes);
- } else {
- // 依據(jù)事件的優(yōu)先級去計算schedulerLanePriority
- const schedulerLanePriority = schedulerPriorityToLanePriority(
- schedulerPriority,
- );
- ...
- // 根據(jù)事件優(yōu)先級計算得來的schedulerLanePriority,去計算更新優(yōu)先級
- lane = findUpdateLane(schedulerLanePriority, currentEventWipLanes);
- }
- return lane;
- }
getCurrentPriorityLevel負(fù)責(zé)讀取記錄在Scheduler中的優(yōu)先級:
- function unstable_getCurrentPriorityLevel() {
- return currentPriorityLevel;
- }
update對象創(chuàng)建完成后意味著需要對頁面進(jìn)行更新,會調(diào)用scheduleUpdateOnFiber進(jìn)入調(diào)度,而真正開始調(diào)度之前會計算本次產(chǎn)生的更新任務(wù)的任務(wù)優(yōu)先級,目的是與已有任務(wù)的任務(wù)優(yōu)先級去做比較,便于做出多任務(wù)的調(diào)度決策。
調(diào)度決策的邏輯在ensureRootIsScheduled 函數(shù)中,這是一個非常重要的函數(shù),控制著React任務(wù)進(jìn)入Scheduler的大門。
任務(wù)優(yōu)先級
一個update會被一個React的更新任務(wù)執(zhí)行掉,任務(wù)優(yōu)先級被用來區(qū)分多個更新任務(wù)的緊急程度,它由更新優(yōu)先級計算而來,舉例來說:
假設(shè)產(chǎn)生一前一后兩個update,它們持有各自的更新優(yōu)先級,也會被各自的更新任務(wù)執(zhí)行。經(jīng)過優(yōu)先級計算,如果后者的任務(wù)優(yōu)先級高于前者的任務(wù)優(yōu)先級,那么會讓Scheduler取消前者的任務(wù)調(diào)度;如果后者的任務(wù)優(yōu)先級等于前者的任務(wù)優(yōu)先級,后者不會導(dǎo)致前者被取消,而是會復(fù)用前者的更新任務(wù),將兩個同等優(yōu)先級的更新收斂到一次任務(wù)中;如果后者的任務(wù)優(yōu)先級低于前者的任務(wù)優(yōu)先級,同樣不會導(dǎo)致前者的任務(wù)被取消,而是在前者更新完成后,再次用Scheduler對后者發(fā)起一次任務(wù)調(diào)度。
這是任務(wù)優(yōu)先級存在的意義,保證高優(yōu)先級任務(wù)及時響應(yīng),收斂同等優(yōu)先級的任務(wù)調(diào)度。
任務(wù)優(yōu)先級在即將調(diào)度的時候去計算,代碼在ensureRootIsScheduled函數(shù)中:
- function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
- ...
- // 獲取nextLanes,順便計算任務(wù)優(yōu)先級
- const nextLanes = getNextLanes(
- root,
- root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
- );
- // 獲取上面計算得出的任務(wù)優(yōu)先級
- const newCallbackPriority = returnNextLanesPriority();
- ...
- }
通過調(diào)用getNextLanes去計算在本次更新中應(yīng)該處理的這批lanes(nextLanes),getNextLanes會調(diào)用getHighestPriorityLanes去計算任務(wù)優(yōu)先級。任務(wù)優(yōu)先級計算的原理是這樣:更新優(yōu)先級(update的lane),它會被并入root.pendingLanes,root.pendingLanes經(jīng)過getNextLanes處理后,挑出那些應(yīng)該處理的lanes,傳入getHighestPriorityLanes,根據(jù)nextLanes找出這些lanes的優(yōu)先級作為任務(wù)優(yōu)先級。
- function getHighestPriorityLanes(lanes: Lanes | Lane): Lanes { ...
- // 都是這種比較賦值的過程,這里只保留兩個以做簡要說明
- const inputDiscreteLanes = InputDiscreteLanes & lanes;
- if (inputDiscreteLanes !== NoLanes) {
- return_highestLanePriority = InputDiscreteLanePriority;
- return inputDiscreteLanes;
- }
- if ((lanes & InputContinuousHydrationLane) !== NoLanes) {
- return_highestLanePriority = InputContinuousHydrationLanePriority;
- return InputContinuousHydrationLane;
- }
- ...
- return lanes;
- }
getHighestPriorityLanes的源碼在這里,getNextLanes的源碼在這里
return_highestLanePriority就是任務(wù)優(yōu)先級,它有如下這些值,值越大,優(yōu)先級越高,暫時只理解任務(wù)優(yōu)先級的作用即可。
- export const SyncLanePriority: LanePriority = 17;
- export const SyncBatchedLanePriority: LanePriority = 16;
- const InputDiscreteHydrationLanePriority: LanePriority = 15;
- export const InputDiscreteLanePriority: LanePriority = 14;
- const InputContinuousHydrationLanePriority: LanePriority = 13;
- export const InputContinuousLanePriority: LanePriority = 12;
- const DefaultHydrationLanePriority: LanePriority = 11;
- export const DefaultLanePriority: LanePriority = 10;
- const TransitionShortHydrationLanePriority: LanePriority = 9;
- export const TransitionShortLanePriority: LanePriority = 8;
- const TransitionLongHydrationLanePriority: LanePriority = 7;
- export const TransitionLongLanePriority: LanePriority = 6;
- const RetryLanePriority: LanePriority = 5;
- const SelectiveHydrationLanePriority: LanePriority = 4;
- const IdleHydrationLanePriority: LanePriority = 3;
- const IdleLanePriority: LanePriority = 2;
- const OffscreenLanePriority: LanePriority = 1;
- export const NoLanePriority: LanePriority = 0;
如果已經(jīng)存在一個更新任務(wù),ensureRootIsScheduled會在獲取到新任務(wù)的任務(wù)優(yōu)先級之后,去和舊任務(wù)的任務(wù)優(yōu)先級去比較,從而做出是否需要重新發(fā)起調(diào)度的決定,若需要發(fā)起調(diào)度,那么會去計算調(diào)度優(yōu)先級。
調(diào)度優(yōu)先級
一旦任務(wù)被調(diào)度,那么它就會進(jìn)入Scheduler,在Scheduler中,這個任務(wù)會被包裝一下,生成一個屬于Scheduler自己的task,這個task持有的優(yōu)先級就是調(diào)度優(yōu)先級。
它有什么作用呢?在Scheduler中,分別用過期任務(wù)隊列和未過期任務(wù)的隊列去管理它內(nèi)部的task,過期任務(wù)的隊列中的task根據(jù)過期時間去排序,最早過期的排在前面,便于被最先處理。而過期時間是由調(diào)度優(yōu)先級計算得出的,不同的調(diào)度優(yōu)先級對應(yīng)的過期時間不同。
調(diào)度優(yōu)先級由任務(wù)優(yōu)先級計算得出,在ensureRootIsScheduled更新真正讓Scheduler發(fā)起調(diào)度的時候,會去計算調(diào)度優(yōu)先級。
- function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
- ...
- // 根據(jù)任務(wù)優(yōu)先級獲取Scheduler的調(diào)度優(yōu)先級
- const schedulerPriorityLevel = lanePriorityToSchedulerPriority(
- newCallbackPriority,
- );
- // 計算出調(diào)度優(yōu)先級之后,開始讓Scheduler調(diào)度React的更新任務(wù)
- newCallbackNode = scheduleCallback(
- schedulerPriorityLevel,
- performConcurrentWorkOnRoot.bind(null, root),
- );
- ...
- }
lanePriorityToSchedulerPriority計算調(diào)度優(yōu)先級的過程是根據(jù)任務(wù)優(yōu)先級找出對應(yīng)的調(diào)度優(yōu)先級。
- export function lanePriorityToSchedulerPriority(
- lanePriority: LanePriority,
- ): ReactPriorityLevel {
- switch (lanePriority) {
- case SyncLanePriority:
- case SyncBatchedLanePriority:
- return ImmediateSchedulerPriority;
- case InputDiscreteHydrationLanePriority:
- case InputDiscreteLanePriority:
- case InputContinuousHydrationLanePriority:
- case InputContinuousLanePriority:
- return UserBlockingSchedulerPriority;
- case DefaultHydrationLanePriority:
- case DefaultLanePriority:
- case TransitionShortHydrationLanePriority:
- case TransitionShortLanePriority:
- case TransitionLongHydrationLanePriority:
- case TransitionLongLanePriority:
- case SelectiveHydrationLanePriority:
- case RetryLanePriority:
- return NormalSchedulerPriority;
- case IdleHydrationLanePriority:
- case IdleLanePriority:
- case OffscreenLanePriority:
- return IdleSchedulerPriority;
- case NoLanePriority:
- return NoSchedulerPriority;
- default:
- invariant(
- false,
- 'Invalid update priority: %s. This is a bug in React.',
- lanePriority,
- );
- }
- }
總結(jié)
本文一共提到了4種優(yōu)先級:事件優(yōu)先級、更新優(yōu)先級、任務(wù)優(yōu)先級、調(diào)度優(yōu)先級,它們之間是遞進(jìn)的關(guān)系。事件優(yōu)先級由事件本身決定,更新優(yōu)先級由事件計算得出,然后放到root.pendingLanes,任務(wù)優(yōu)先級來自root.pendingLanes中最緊急的那些lanes對應(yīng)的優(yōu)先級,調(diào)度優(yōu)先級根據(jù)任務(wù)優(yōu)先級獲取。幾種優(yōu)先級環(huán)環(huán)相扣,保證了高優(yōu)任務(wù)的優(yōu)先執(zhí)行。