點擊進入React源碼調試倉庫。
UI產生交互的根本原因是各種事件,這也就意味著事件與更新有著直接關系。不同事件產生的更新,它們的優先級是有差異的,所以更新優先級的根源在於事件的優先級。壹個更新的產生可直接導致React生成壹個更新任務,最終這個任務被Scheduler調度。
所以在React中,人為地將事件劃分了等級,最終目的是決定調度任務的輕重緩急,因此,React有壹套從事件到調度的優先級機制。
本文將圍繞事件優先級、更新優先級、任務優先級、調度優先級,重點梳理它們之間的轉化關系。
前三者屬於React的優先級機制,第四個屬於Scheduler的優先級機制,Scheduler內部有自己的優先級機制,雖然與React有所區別,但等級的劃分基本壹致。下面我們從事件優先級開始說起。
React按照事件的緊急程度,把它們劃分成三個等級:
事件優先級是在註冊階段被確定的,在向root上註冊事件時,會根據事件的類別,創建不同優先級的事件監聽(listener),最終將它綁定到root上去。
let listener = createEventListenerWrapperWithPriority(
targetContainer,
domEventName,
eventSystemFlags,
listenerPriority,
);
createEventListenerWrapperWithPriority
函數的名字已經把它做的事情交代得八九不離十了。它會首先根據事件的名稱去找對應的事件優先級,然後依據優先級返回不同的事件監聽函數。
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上的事件監聽其實是dispatchDiscreteEvent、dispatchUserBlockingUpdate、dispatchEvent這三個中的壹個。它們做的事情都是壹樣的,以各自的事件優先級去執行真正的事件處理函數。
比如:dispatchDiscreteEvent
和dispatchUserBlockingUpdate
最終都會以UserBlockingEvent的事件級別去執行事件處理函數。
以某種優先級去執行事件處理函數其實要借助Scheduler中提供的runWithPriority
函數來實現:
function dispatchUserBlockingUpdate(
domEventName,
eventSystemFlags,
container,
nativeEvent,
) {
...
runWithPriority(
UserBlockingPriority,
dispatchEvent.bind(
null,
domEventName,
eventSystemFlags,
container,
nativeEvent,
),
);
...
}
這麽做可以將事件優先級記錄到Scheduler中,相當於告訴Scheduler:妳幫我記錄壹下當前事件派發的優先級,等React那邊創建更新對象(即update)計算更新優先級時直接從妳這拿就好了。
function unstable_runWithPriority(priorityLevel, eventHandler) {
switch (priorityLevel) {
case ImmediatePriority:
case UserBlockingPriority:
case NormalPriority:
case LowPriority:
case IdlePriority:
break;
default:
priorityLevel = NormalPriority;
}
var previousPriorityLevel = currentPriorityLevel;
// 記錄優先級到Scheduler內部的變量裏
currentPriorityLevel = priorityLevel;
try {
return eventHandler();
} finally {
currentPriorityLevel = previousPriorityLevel;
}
}
以setState為例,事件的執行會導致setState執行,而setState本質上是調用enqueueSetState,生成壹個update對象,這時候會計算它的更新優先級,即update.lane:
const classComponentUpdater = {
enqueueSetState(inst, payload, callback) {
...
// 依據事件優先級創建update的優先級
const lane = requestUpdateLane(fiber, suspenseConfig);
const update = createUpdate(eventTime, lane, suspenseConfig);
update.payload = payload;
enqueueUpdate(fiber, update);
// 開始調度
scheduleUpdateOnFiber(fiber, lane, eventTime);
...
},
};
重點關註requestUpdateLane,它首先找出Scheduler中記錄的優先級:schedulerPriority,然後計算更新優先級:lane,具體的計算過程在findUpdateLane函數中,計算過程是壹個從高到低依次占用空閑位的操作,具體的代碼在這裏 ,這裏就先不詳細展開。
export function requestUpdateLane(
fiber: Fiber,
suspenseConfig: SuspenseConfig | null,
): Lane {
...
// 根據記錄下的事件優先級,獲取任務調度優先級
const schedulerPriority = getCurrentPriorityLevel();
let lane;
if (
(executionContext & DiscreteEventContext) !== NoContext &&
schedulerPriority === UserBlockingSchedulerPriority
) {
// 如果事件優先級是用戶阻塞級別,則直接用InputDiscreteLanePriority去計算更新優先級
lane = findUpdateLane(InputDiscreteLanePriority, currentEventWipLanes);
} else {
// 依據事件的優先級去計算schedulerLanePriority
const schedulerLanePriority = schedulerPriorityToLanePriority(
schedulerPriority,
);
...
// 根據事件優先級計算得來的schedulerLanePriority,去計算更新優先級
lane = findUpdateLane(schedulerLanePriority, currentEventWipLanes);
}
return lane;
}
getCurrentPriorityLevel
負責讀取記錄在Scheduler中的優先級:
function unstable_getCurrentPriorityLevel() {
return currentPriorityLevel;
}
update對象創建完成後意味著需要對頁面進行更新,會調用scheduleUpdateOnFiber進入調度,而真正開始調度之前會計算本次產生的更新任務的任務優先級,目的是與已有任務的任務優先級去做比較,便於做出多任務的調度決策。
調度決策的邏輯在ensureRootIsScheduled 函數中,這是壹個非常重要的函數,控制著React任務進入Scheduler的大門。
壹個update會被壹個React的更新任務執行掉,任務優先級被用來區分多個更新任務的緊急程度,它由更新優先級計算而來,舉例來說:
假設產生壹前壹後兩個update,它們持有各自的更新優先級,也會被各自的更新任務執行。經過優先級計算,如果後者的任務優先級高於前者的任務優先級,那麽會讓Scheduler取消前者的任務調度;如果後者的任務優先級等於前者的任務優先級,後者不會導致前者被取消,而是會復用前者的更新任務,將兩個同等優先級的更新收斂到壹次任務中;如果後者的任務優先級低於前者的任務優先級,同樣不會導致前者的任務被取消,而是在前者更新完成後,再次用Scheduler對後者發起壹次任務調度。
這是任務優先級存在的意義,保證高優先級任務及時響應,收斂同等優先級的任務調度。
任務優先級在即將調度的時候去計算,代碼在ensureRootIsScheduled
函數中:
function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
...
// 獲取nextLanes,順便計算任務優先級
const nextLanes = getNextLanes(
root,
root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
);
// 獲取上面計算得出的任務優先級
const newCallbackPriority = returnNextLanesPriority();
...
}
通過調用getNextLanes去計算在本次更新中應該處理的這批lanes(nextLanes),getNextLanes會調用getHighestPriorityLanes去計算任務優先級。任務優先級計算的原理是這樣:更新優先級(update的lane),它會被並入root.pendingLanes,root.pendingLanes經過getNextLanes處理後,挑出那些應該處理的lanes,傳入getHighestPriorityLanes
,根據nextLanes找出這些lanes的優先級作為任務優先級。
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
就是任務優先級,它有如下這些值,值越大,優先級越高,暫時只理解任務優先級的作用即可。
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;
如果已經存在壹個更新任務,ensureRootIsScheduled
會在獲取到新任務的任務優先級之後,去和舊任務的任務優先級去比較,從而做出是否需要重新發起調度的決定,若需要發起調度,那麽會去計算調度優先級。
壹旦任務被調度,那麽它就會進入Scheduler,在Scheduler中,這個任務會被包裝壹下,生成壹個屬於Scheduler自己的task,這個task持有的優先級就是調度優先級。
它有什麽作用呢?在Scheduler中,分別用過期任務隊列和未過期任務的隊列去管理它內部的task,過期任務的隊列中的task根據過期時間去排序,最早過期的排在前面,便於被最先處理。而過期時間是由調度優先級計算的出的,不同的調度優先級對應的過期時間不同。
調度優先級由任務優先級計算得出,在ensureRootIsScheduled
更新真正讓Scheduler發起調度的時候,會去計算調度優先級。
function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
...
// 根據任務優先級獲取Scheduler的調度優先級
const schedulerPriorityLevel = lanePriorityToSchedulerPriority(
newCallbackPriority,
);
// 計算出調度優先級之後,開始讓Scheduler調度React的更新任務
newCallbackNode = scheduleCallback(
schedulerPriorityLevel,
performConcurrentWorkOnRoot.bind(null, root),
);
...
}
lanePriorityToSchedulerPriority
計算調度優先級的過程是根據任務優先級找出對應的調度優先級。
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,
);
}
}
本文壹共提到了4種優先級:事件優先級、更新優先級、任務優先級、調度優先級,它們之間是遞進的關系。事件優先級由事件本身決定,更新優先級由事件計算得出,然後放到root.pendingLanes,任務優先級來自root.pendingLanes中最緊急的那些lanes對應的優先級,調度優先級根據任務優先級獲取。幾種優先級環環相扣,保證了高優任務的優先執行。