天天看點

React中的4種優先級

React中的4種優先級

UI産生互動的根本原因是各種事件,這也就意味着事件與更新有着直接關系。不同僚件産生的更新,它們的優先級是有差異的,是以更新優先級的根源在于事件的優先級。一個更新的産生可直接導緻react生成一個更新任務,最終這個任務被Scheduler排程。

是以在React中,人為地将事件劃分了等級,最終目的是決定排程任務的輕重緩急,是以,React有一套從事件到排程的優先級機制。

本文将圍繞事件優先級、更新優先級、任務優先級、排程優先級,重點梳理它們之間的轉化關系。

  • 事件優先級:按照使用者事件的互動緊急程度,劃分的優先級
  • 更新優先級:事件導緻React産生的更新對象(update)的優先級(update.lane)
  • 任務優先級:産生更新對象之後,React去執行一個更新任務,這個任務所持有的優先級
  • 排程優先級:Scheduler依據React更新任務生成一個排程任務,這個排程任務所持有的優先級

前三者屬于React的優先級機制,第四個屬于Scheduler的優先級機制,Scheduler内部有自己的優先級機制,雖然與React有所差別,但等級的劃分基本一緻。下面我們從事件優先級開始說起。

優先級的起點:事件優先級

React按照事件的緊急程度,把它們劃分成三個等級:

  • 離散事件(DiscreteEvent):click、keydown、focusin等,這些事件的觸發不是連續的,優先級為0。
  • 使用者阻塞事件(UserBlockingEvent):drag、scroll、mouseover等,特點是連續觸發,阻塞渲染,優先級為1。
  • 連續事件(ContinuousEvent):canplay、error、audio标簽的timeupdate和canplay,優先級最高,為2。

派發事件優先級​

事件優先級是在注冊階段被确定的,在向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對應的優先級,排程優先級根據任務優先級擷取。幾種優先級環環相扣,保證了高優任務的優先執行。

本文完〜