Happy New Year, a little floating, very bad state. Slowly adjust from learning.

I sorted out the Fiber architecture of React before, but I didn’t have a deep understanding of the concept of priority in the architecture, so I couldn’t understand some details of the scheduling process. So, based on the source code and my own understanding of the following article, from the perspective of priority scheduling process. The click of a spray.


The source code is based on 16.8.6

Summary:

  1. Priority Introduction
  2. Event priority and event distribution mechanism
  3. Update priority
  4. Task priority
  5. Scheduling priority
  6. more

I. Introduction of priorities

Different events result in different updates (Lanes), which are collected into tasks that are scheduled hierarchically.

React has four priorities:

  1. Event priority,
  2. Update priority (Lane),
  3. Task priority,
  4. Scheduling priority (Scheulder)

2. Event priority and event distribution mechanism

1. Event priority

Event priorities fall into three categories:

  1. Discreteevents are events that are not continuously triggered. The priority of these events is 0. Like Click, focus, keyDown
  2. UserBlockingEvent, a sequential event with priority 1. So scroll, drag, mouseOut
  3. ContinousEvent (continousEvent), a non-triggered continuous event with the highest priority of 2. Such as Process, playing, and load

Reference source: packages/react – dom/SRC/event/DOMEventProperties js

const discreteEventPairsForSimpleEventPlugin = [
  ('cancel': DOMEventName), 'cancel',
  ('click': DOMEventName), 'click',
  ('close': DOMEventName), 'close',
  ('contextmenu': DOMEventName), 'contextMenu',
  ('copy': DOMEventName), 'copy',
  ('cut': DOMEventName), 'cut',
  ('auxclick': DOMEventName), 'auxClick',
  ('dblclick': DOMEventName), 'doubleClick'.// Careful!
  ('dragend': DOMEventName), 'dragEnd',
  ('dragstart': DOMEventName), 'dragStart',
  ('drop': DOMEventName), 'drop',
  ('focusin': DOMEventName), 'focus'.// Careful!
  ('focusout': DOMEventName), 'blur'.// Careful!
  ('input': DOMEventName), 'input',
  ('invalid': DOMEventName), 'invalid',
  ('keydown': DOMEventName), 'keyDown',
  ('keypress': DOMEventName), 'keyPress',
  ('keyup': DOMEventName), 'keyUp',
  ('mousedown': DOMEventName), 'mouseDown',
  ('mouseup': DOMEventName), 'mouseUp',
  ('paste': DOMEventName), 'paste',
  ('pause': DOMEventName), 'pause',
  ('play': DOMEventName), 'play',
  ('pointercancel': DOMEventName), 'pointerCancel',
  ('pointerdown': DOMEventName), 'pointerDown',
  ('pointerup': DOMEventName), 'pointerUp',
  ('ratechange': DOMEventName), 'rateChange',
  ('reset': DOMEventName), 'reset',
  ('seeked': DOMEventName), 'seeked',
  ('submit': DOMEventName), 'submit',
  ('touchcancel': DOMEventName), 'touchCancel',
  ('touchend': DOMEventName), 'touchEnd',
  ('touchstart': DOMEventName), 'touchStart',
  ('volumechange': DOMEventName), 'volumeChange',];const otherDiscreteEvents: Array<DOMEventName> = [
  'change'.'selectionchange'.'textInput'.'compositionstart'.'compositionend'.'compositionupdate',];// prettier-ignore
const userBlockingPairsForSimpleEventPlugin: Array<string | DOMEventName> = [
  ('drag': DOMEventName), 'drag',
  ('dragenter': DOMEventName), 'dragEnter',
  ('dragexit': DOMEventName), 'dragExit',
  ('dragleave': DOMEventName), 'dragLeave',
  ('dragover': DOMEventName), 'dragOver',
  ('mousemove': DOMEventName), 'mouseMove',
  ('mouseout': DOMEventName), 'mouseOut',
  ('mouseover': DOMEventName), 'mouseOver',
  ('pointermove': DOMEventName), 'pointerMove',
  ('pointerout': DOMEventName), 'pointerOut',
  ('pointerover': DOMEventName), 'pointerOver',
  ('scroll': DOMEventName), 'scroll',
  ('toggle': DOMEventName), 'toggle',
  ('touchmove': DOMEventName), 'touchMove',
  ('wheel': DOMEventName), 'wheel',];const continuousPairsForSimpleEventPlugin: Array<string | DOMEventName> = [
  ('abort': DOMEventName), 'abort',
  (ANIMATION_END: DOMEventName), 'animationEnd',
  (ANIMATION_ITERATION: DOMEventName), 'animationIteration',
  (ANIMATION_START: DOMEventName), 'animationStart',
  ('canplay': DOMEventName), 'canPlay',
  ('canplaythrough': DOMEventName), 'canPlayThrough',
  ('durationchange': DOMEventName), 'durationChange',
  ('emptied': DOMEventName), 'emptied',
  ('encrypted': DOMEventName), 'encrypted',
  ('ended': DOMEventName), 'ended',
  ('error': DOMEventName), 'error',
  ('gotpointercapture': DOMEventName), 'gotPointerCapture',
  ('load': DOMEventName), 'load',
  ('loadeddata': DOMEventName), 'loadedData',
  ('loadedmetadata': DOMEventName), 'loadedMetadata',
  ('loadstart': DOMEventName), 'loadStart',
  ('lostpointercapture': DOMEventName), 'lostPointerCapture',
  ('playing': DOMEventName), 'playing',
  ('progress': DOMEventName), 'progress',
  ('seeking': DOMEventName), 'seeking',
  ('stalled': DOMEventName), 'stalled',
  ('suspend': DOMEventName), 'suspend',
  ('timeupdate': DOMEventName), 'timeUpdate',
  (TRANSITION_END: DOMEventName), 'transitionEnd',
  ('waiting': DOMEventName), 'waiting',];Copy the code

2. Event distribution mechanism

  1. The event priority is created based on the event namedomEventNameTo determine the
  2. According to the methodgetEventPriorityForPluginSystemAfter the event priority is calculated, different listening events are determined to be invoked
  3. And userunWithPriorityWrites the scheduling priority corresponding to the scheduling event prioritySchedulerFor calculating the update priority
export function createEventListenerWrapperWithPriority(targetContainer: EventTarget, domEventName: DOMEventName, eventSystemFlags: EventSystemFlags,) :Function {
  // Calculate the event priority according to domEventName
  const eventPriority = getEventPriorityForPluginSystem(domEventName);
  let listenerWrapper;
  // Call different listener functions according to different event priorities
  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,
  );
}
Copy the code

packages/scheduler/Scheduler.js

function unstable_runWithPriority(priorityLevel, eventHandler) {

  var previousPriorityLevel = currentPriorityLevel;
  currentPriorityLevel = priorityLevel;

  try {
    return eventHandler();
  } finally{ currentPriorityLevel = previousPriorityLevel; }}Copy the code

3. Update priorities

The general steps for calculating the update priority are as follows:

  1. Task priority creation depends on the current scheduling priority (that is, the current scheduling situation).

  2. Scheduler_getCurrentPriorityLevel Scheduler_getCurrentPriorityLevel Scheduler_getCurrentPriorityLevel Scheduler_getCurrentPriorityLevel schedulerPriority schedulerPriority This is reflected in the function getCurrentPriorityLevel.

  3. Secondly according to get the react of internal scheduling priority, into a lane priority schedulerLanePriority, reflect function in the schedulerPriorityToLanePriority. Among them it is worth noting that the update task for discrete event processing, its special handling for InputDiscreteLanePriority

  4. I will change the priority of each lane to that of the updated lane. I will change the priority of each lane to that of the updated lane. I will change the priority of each lane to that of the updated lane. Manifest the function in findUpdateLane

  5. Without further discussion, let’s focus on the calculation of lane, as reflected in the requestUpdateLane(Fiber) function.

export function requestUpdateLane(fiber: Fiber) :Lane {
  // Special cases
  //...
  // TODO: Remove this dependency on the Scheduler priority.
  // To do that, we're replacing it with an update lane priority.
  const schedulerPriority = getCurrentPriorityLevel();
  
  / / using the Schedule of the priority and react within the priority of the coupling, so use currentUpdateLanePriority instead.
  / /, for example, if not the Scheduler runWithPriority, when we get priority get priority of running the task Scheduler.
  let lane;
  if (
    / / personal understanding: discrete event trigger the task of special handling, return InputDiscreteLanePriority
    / / user blocking and continuous event task in the following schedulerPriorityToLanePriority processing, returns InputContinuousLanePriority(executionContext & DiscreteEventContext) ! == NoContext && schedulerPriority === UserBlockingSchedulerPriority ) { lane = findUpdateLane(InputDiscreteLanePriority,  currentEventWipLanes); }else {
    const schedulerLanePriority = schedulerPriorityToLanePriority(
      schedulerPriority,
    );
    / /... omit
    lane = findUpdateLane(schedulerLanePriority, currentEventWipLanes);
  }

  return lane;
}
Copy the code
  1. Let’s seegetCurrentPriorityLevelThe React () function converts the priority in Schedule to the priority in React
/ / source location: packages/react - the reconciler/SRC/SchedulerWithReactIntegration. Old. Js
export function getCurrentPriorityLevel() :ReactPriorityLevel {
  switch (Scheduler_getCurrentPriorityLevel()) {
    case Scheduler_ImmediatePriority:
      return ImmediatePriority;
    case Scheduler_UserBlockingPriority:
      return UserBlockingPriority;
    case Scheduler_NormalPriority:
      return NormalPriority;
    case Scheduler_LowPriority:
      return LowPriority;
    case Scheduler_IdlePriority:
      return IdlePriority;
    default:
      invariant(false.'Unknown priority level.'); }}Copy the code
// Priority in schedule
export const unstable_ImmediatePriority = 1;
export const unstable_UserBlockingPriority = 2;
export const unstable_NormalPriority = 3;
export const unstable_IdlePriority = 5;
export const unstable_LowPriority = 4;

// React priorities
export const ImmediatePriority: ReactPriorityLevel = 99;
export const UserBlockingPriority: ReactPriorityLevel = 98;
export const NormalPriority: ReactPriorityLevel = 97;
export const LowPriority: ReactPriorityLevel = 96;
export const IdlePriority: ReactPriorityLevel = 95;
Copy the code
  1. Let’s look at it againschedulerPriorityToLanePriorityThis function converts schedulePriority to scheduleLanePriority
export function schedulerPriorityToLanePriority(
  schedulerPriorityLevel: ReactPriorityLevel,
) :LanePriority {
  switch (schedulerPriorityLevel) {
    case ImmediateSchedulerPriority:
      return SyncLanePriority;
    case UserBlockingSchedulerPriority:
      return InputContinuousLanePriority;
    case NormalSchedulerPriority:
    case LowSchedulerPriority:
      // TODO: Handle LowSchedulerPriority, somehow. Maybe the same lane as hydration.
      return DefaultLanePriority;
    case IdleSchedulerPriority:
      return IdleLanePriority;
    default:
      returnNoLanePriority; }}Copy the code
  1. Finally, let’s look at the most importantfindUpdateLaneFunction to find the free lane (~wipLanes), the highest priority lane (lanes & -lanes)
export function findUpdateLane(lanePriority: LanePriority, wipLanes: Lanes,) :Lane {
  switch (lanePriority) {
    case NoLanePriority:
      break;
    case SyncLanePriority:
      return SyncLane;
    case SyncBatchedLanePriority:
      return SyncBatchedLane;
    case InputDiscreteLanePriority: {
      const lane = pickArbitraryLane(InputDiscreteLanes & ~wipLanes);
      if (lane === NoLane) {
        return findUpdateLane(InputContinuousLanePriority, wipLanes);
      }
      return lane;
    }
    case InputContinuousLanePriority: {
      const lane = pickArbitraryLane(InputContinuousLanes & ~wipLanes);
      if (lane === NoLane) {
        return findUpdateLane(DefaultLanePriority, wipLanes);
      }
      return lane;
    }
    case DefaultLanePriority: {
      let lane = pickArbitraryLane(DefaultLanes & ~wipLanes);
      if (lane === NoLane) {
        lane = pickArbitraryLane(TransitionLanes & ~wipLanes);
        if(lane === NoLane) { lane = pickArbitraryLane(DefaultLanes); }}return lane;
    }
    case TransitionPriority: // Should be handled by findTransitionLane instead
    case RetryLanePriority: // Should be handled by findRetryLane instead
      break;
    case IdleLanePriority:
      let lane = pickArbitraryLane(IdleLanes & ~wipLanes);
      if (lane === NoLane) {
        lane = pickArbitraryLane(IdleLanes);
      }
      return lane;
    default:
      break; }}Copy the code

The essence of an update is to create an Update object, calculate a lane for it, and store the update object as a single necklace in the sharedQueue property of the current Fiber object.

Task priority

  1. First of all to enterscheduleUpdateOnFiber, this function is the entry point for all scheduled tasks.
  2. Take the lane obtained in the previous step and merge the lanes toroot.pendingLanes, merge and mark the entire Fiber tree and set expiration times for each lane
  3. After that, the task priority is calculated, and the task with expired lanes is dealt with first to prevent the task with low priority from starving to death.
  4. The calculation logic is mainly processed by lane types from pendingLanes (idle tasks, non-idle tasks).
  5. Note that when rendering, only the current high-priority lane task can preempt the low-priority lane task

1. Entry function for all schedules, start of all schedules

export function scheduleUpdateOnFiber(fiber: Fiber, lane: Lane, eventTime: number,) {
  checkForNestedUpdates();
  // Will merge with the new priority up to root
  const root = markUpdateLaneFromFiberToRoot(fiber, lane);
  // Merge the update priorities
  markRootUpdated(root, lane, eventTime);
  
  // select * from priority;
}
Copy the code

2. Mark the lane of the entire Fiber tree node

export function markRootUpdated(root: FiberRoot, updateLane: Lane, eventTime: number,) {
    // The priority collection will be updated
    root.pendingLanes |= updateLane;
    // Do not suspend updates of the same or lower priority
    const higherPriorityLanes = updateLane - 1; 
    root.suspendedLanes &= higherPriorityLanes;
    root.pingedLanes &= higherPriorityLanes;
    // Collect the task update start time
    const eventTimes = root.eventTimes;
    const index = laneToIndex(updateLane);
    eventTimes[index] = eventTime;
}
Copy the code

3. Prioritize tasks

export function getNextLanes(root: FiberRoot, wipLanes: Lanes) :Lanes {
  const pendingLanes = root.pendingLanes;  
  let nextLanes = NoLanes;
  let nextLanePriority = NoLanePriority;

  const expiredLanes = root.expiredLanes;
  const suspendedLanes = root.suspendedLanes;
  const pingedLanes = root.pingedLanes;

  // Handle low priority tasks and prevent starvation
  if(expiredLanes ! == NoLanes) { nextLanes = expiredLanes; nextLanePriority = return_highestLanePriority = SyncLanePriority; }else {
   // Calculate the next quest's lane and lane priority
    const nonIdlePendingLanes = pendingLanes & NonIdleLanes;
    if(nonIdlePendingLanes ! == NoLanes) {const nonIdleUnblockedLanes = nonIdlePendingLanes & ~suspendedLanes;
      if(nonIdleUnblockedLanes ! == NoLanes) {// Non-idle, non-blocking??
        nextLanes = getHighestPriorityLanes(nonIdleUnblockedLanes);
        nextLanePriority = return_highestLanePriority;
      } else {
      	// Not idle, paused
        const nonIdlePingedLanes = nonIdlePendingLanes & pingedLanes;
        if(nonIdlePingedLanes ! == NoLanes) { nextLanes = getHighestPriorityLanes(nonIdlePingedLanes); nextLanePriority = return_highestLanePriority; }}}else {
      // Processing of idle tasks
      const unblockedLanes = pendingLanes & ~suspendedLanes;
      if(unblockedLanes ! == NoLanes) { nextLanes = getHighestPriorityLanes(unblockedLanes); nextLanePriority = return_highestLanePriority; }else {
        if(pingedLanes ! == NoLanes) { nextLanes = getHighestPriorityLanes(pingedLanes); nextLanePriority = return_highestLanePriority; } } } } nextLanes = pendingLanes & getEqualOrHigherPriorityLanes(nextLanes);// If the task is rendering, the higher priority task preempts the lower priority task
  if( wipLanes ! == NoLanes && wipLanes ! == nextLanes && (wipLanes & suspendedLanes) === NoLanes ) { getHighestPriorityLanes(wipLanes);const wipLanePriority = return_highestLanePriority;
    if (nextLanePriority <= wipLanePriority) {
      return wipLanes;
    } else{ return_highestLanePriority = nextLanePriority; }}return nextLanes;
}
Copy the code

5. The core method of task scheduling

function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
  const existingCallbackNode = root.callbackNode;
  
  / / collection lanes root due. ExpiredLanes | = lane;
  markStarvedLanesAsExpired(root, currentTime);

  // Calculate task priority, next available task lanes
  const nextLanes = getNextLanes(
    root,
    root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
  );
  // Calculates the priority of the new task
  const newCallbackPriority = returnNextLanesPriority();

  // Select * from the list where the priority logic is calculated; // Select * from the list where the priority logic is calculated
  / / to omit...
  
  
  // After a new task is generated, the task priority is assigned
  root.callbackPriority = newCallbackPriority;
  root.callbackNode = newCallbackNode;
}
Copy the code

The point of task priority is to compare and process the update logic of old and new priorities. The new task with a higher priority preempts the old task with a lower priority, the new task with the same priority converges, and the new task with a lower priority waits for the higher priority task.

5. Scheduling priority

  1. The scheduling priority isSchedulerInternal priorities

pachages/scheduler/src/SchedulerPriorities.js

export type PriorityLevel = 0 | 1 | 2 | 3 | 4 | 5;

// TODO: Use symbols?
export const NoPriority = 0;
export const ImmediatePriority = 1;
export const UserBlockingPriority = 2;
export const NormalPriority = 3;
export const LowPriority = 4;
export const IdlePriority = 5;

Copy the code
  1. Task scheduling creates tasks with different delays according to their priorities
function unstable_scheduleCallback(priorityLevel, callback, options) {
  var currentTime = getCurrentTime();

  var startTime;
  if (typeof options === 'object'&& options ! = =null) {
    var delay = options.delay;
    if (typeof delay === 'number' && delay > 0) {
      startTime = currentTime + delay;
    } else{ startTime = currentTime; }}else {
    startTime = currentTime;
  }

  var timeout;
  switch (priorityLevel) {
    case ImmediatePriority:
      timeout = IMMEDIATE_PRIORITY_TIMEOUT;
      break;
    case UserBlockingPriority:
      timeout = USER_BLOCKING_PRIORITY_TIMEOUT;
      break;
    case IdlePriority:
      timeout = IDLE_PRIORITY_TIMEOUT;
      break;
    case LowPriority:
      timeout = LOW_PRIORITY_TIMEOUT;
      break;
    case NormalPriority:
    default:
      timeout = NORMAL_PRIORITY_TIMEOUT;
      break;
  }

  var expirationTime = startTime + timeout;
  / / create newtask
  return newTask;
}
Copy the code