React source debug repository

The root cause of UI interaction is events, which means events are directly related to updates. The priority of updates generated by different events is different, so the root of the update priority is the priority of the event. When an update is generated, React generates an update task that is scheduled by the Scheduler.

In React, events are artificially graded to determine the priority of scheduling tasks. Therefore, React has a priority mechanism from events to scheduling.

This paper focuses on sorting out the transformation relationship among event priority, update priority, task priority and scheduling priority.

  • Event priority: Indicates the priorities of user events based on the interaction urgency
  • Update priority: The priority of the update object generated by the React event (update.lane)
  • Task priority: After the update object is generated, React executes an update task. The priority that the task holds
  • Scheduling priority: The priority that the Scheduler generates a scheduling task based on the React update task

The first three belong to the priority mechanism of React, while the fourth belongs to the priority mechanism of Scheduler. Scheduler has its own priority mechanism. Although it is different from React, the classification of grades is basically the same. Let’s start with event priorities.

Priority starting point: event priority

React divides events into three levels based on their urgency:

  • Discreteevents: Click, KeyDown, FocusIn, etc. These events are not triggered consecutively and have a priority of 0.
  • Userblockingevents: Drag, scroll, mouseover, etc., which are triggered continuously to block rendering and have priority 1.
  • ContinuousEvent: Timeupdate and canplay of canplay, error, audio tags, with the highest priority of 2.

The event priority was sent

The event priority is determined during the registration phase. When an event is registered with root, event listeners of different priorities are created according to the event category and eventually bound to root.

let listener = createEventListenerWrapperWithPriority(
    targetContainer,
    domEventName,
    eventSystemFlags,
    listenerPriority,
  );
Copy the code

CreateEventListenerWrapperWithPriority function name already do replacement it too close. It will first find the corresponding event priority based on the name of the event, and then return different event listeners based on the priority.

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,
  );
}

Copy the code

Finally is bound to the root of event listeners dispatchDiscreteEvent, dispatchUserBlockingUpdate, dispatchEvent one of the three. They all do the same thing, executing real event handlers at their respective event priorities. Such as: dispatchDiscreteEvent and dispatchUserBlockingUpdate eventually UserBlockingEvent event level to implement the event handler.

The Scheduler provides a runWithPriority function to execute event handlers at a certain priority:

function dispatchUserBlockingUpdate(domEventName, eventSystemFlags, container, nativeEvent,) {... runWithPriority( UserBlockingPriority, dispatchEvent.bind(null, domEventName, eventSystemFlags, container, nativeEvent, ), ); . }Copy the code

The Scheduler is told to record the priority of the current event and take it from the Scheduler when the React update object calculates the priority.

function unstable_runWithPriority(priorityLevel, eventHandler) {
  switch (priorityLevel) {
    case ImmediatePriority:
    case UserBlockingPriority:
    case NormalPriority:
    case LowPriority:
    case IdlePriority:
      break;
    default:
      priorityLevel = NormalPriority;
  }

  var previousPriorityLevel = currentPriorityLevel;
  // Record the priority to a variable inside the Scheduler
  currentPriorityLevel = priorityLevel;

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

Update priority

In the case of setState, the execution of the event causes setState to be executed, and setState essentially calls enqueueSetState to generate an update object, at which point its update priority is calculated, update. Lane:

const classComponentUpdater = {
  enqueueSetState(inst, payload, callback){...// Create update priorities based on event priorities
    const lane = requestUpdateLane(fiber, suspenseConfig);

    const update = createUpdate(eventTime, lane, suspenseConfig);
    update.payload = payload;
    enqueueUpdate(fiber, update);

    // Start schedulingscheduleUpdateOnFiber(fiber, lane, eventTime); . }};Copy the code

Focus on requestUpdateLane, which first finds the priority recorded in Scheduler: schedulerPriority, and then calculates the update priority: schedulerPriority Lane, the specific calculation process in findUpdateLane function, the calculation process is an operation from high to low occupy free bits in turn, the specific code is here, here will not be expanded in detail.

export function requestUpdateLane(
  fiber: Fiber,
  suspenseConfig: SuspenseConfig | null.) :Lane {...// Obtain the task scheduling priority according to the recorded event priority
  const schedulerPriority = getCurrentPriorityLevel();

  let lane;
  if( (executionContext & DiscreteEventContext) ! == NoContext && schedulerPriority === UserBlockingSchedulerPriority ) {/ / if the event is the user priority level of obstruction, directly to calculate with InputDiscreteLanePriority update priority
    lane = findUpdateLane(InputDiscreteLanePriority, currentEventWipLanes);
  } else {
    // schedulerLanePriority is calculated based on the priority of the event
    constschedulerLanePriority = schedulerPriorityToLanePriority( schedulerPriority, ); .// The schedulerLanePriority calculates the update priority based on the event priority
    lane = findUpdateLane(schedulerLanePriority, currentEventWipLanes);
  }
  return lane;
}
Copy the code

GetCurrentPriorityLevel reads the priority of the record in the Scheduler:

function unstable_getCurrentPriorityLevel() {
  return currentPriorityLevel;
}
Copy the code

After the creation of the update object, the page needs to be updated, so scheduleUpdateOnFiber is called to start scheduling. Before scheduling, the priority of the update task is calculated, so as to compare the priority with that of the existing task, so as to facilitate multi-task scheduling decisions.

Scheduling decision logic inensureRootIsScheduledFunction, which controls the React task’s entrance to Scheduler.

Task priority

A React update task is executed by a React update task. Task priority is used to distinguish the urgency of multiple update tasks.

Suppose two updates are generated, each with its own update priority and executed by its own update task. After priority calculation, if the task priority of the latter is higher than that of the former, the Scheduler will cancel the task scheduling of the former. If the task priority of the latter is equal to the task priority of the former, the latter will not cause the former to be cancelled, but will reuse the update task of the former, and the two updates of the same priority will be converged into one task. If the task priority of the latter is lower than that of the former, the task of the former will not be cancelled, but after the update of the former is completed, the Scheduler will initiate a task scheduling for the latter again.

This is the significance of task priority to ensure timely response of high-priority tasks and convergence of task scheduling with the same priority.

The priority of a task is to be determined when one is about to be scheduled, with the ensureRootIsScheduled function:

function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {...// Get nextLanes and calculate the task priority
  const nextLanes = getNextLanes(
    root,
    root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
  );

  // Get the task priority calculated above
  constnewCallbackPriority = returnNextLanesPriority(); . }Copy the code

By calling getNextLanes to calculate the number of Lanes (nextLanes) that should be processed in this update, getNextLanes will call getHighestPriorityLanes to calculate the task priority. Task priority calculation works like this: Update priority (lane), which is incorporated into root.pendingLanes. After receiving getNextLanes, root.pendingLanes selects the lanes that should be handled and passes to getHighestPriorityLanes. Find out the priority of these Lanes according to nextLanes as the task priority.

function getHighestPriorityLanes(lanes: Lanes | Lane) :Lanes {...// This is the comparison assignment process, only two are reserved here for a brief explanation
  const inputDiscreteLanes = InputDiscreteLanes & lanes;
  if(inputDiscreteLanes ! == NoLanes) { return_highestLanePriority = InputDiscreteLanePriority;return inputDiscreteLanes;
  }
  if((lanes & InputContinuousHydrationLane) ! == NoLanes) { return_highestLanePriority = InputContinuousHydrationLanePriority;returnInputContinuousHydrationLane; }...return lanes;
}
Copy the code

The getHighestPriorityLanesThe source codeHere at getNextLanesThe source codeHere,

Return_highestLanePriority is the task priority, which has the following values: the higher the value, the higher the priority.

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;
Copy the code

If one update task exists, the priority of the new one is “ensureRootIsScheduled” and the priority of the old one is compared with that of the new one to determine whether a dispatching is warranted or if one is warranted, the scheduling priority will be calculated.

Scheduling priority

Once a task is scheduled, it goes to the Scheduler, where the task is wrapped to generate a task that is Scheduler’s own. The task’s priority is the scheduling priority.

What does it do? In Scheduler, the queue of expired tasks and the queue of unexpired tasks are respectively used to manage its internal tasks. The tasks in the queue of expired tasks are sorted according to the expiration time, and the ones with the earliest expiration are placed in front of the queue to facilitate processing. The expiration time is calculated by scheduling priorities. Different scheduling priorities correspond to different expiration times.

Scheduling priorities are calculated from the task priority when one update “ensureRootIsScheduled” actually allows the Scheduler to initiate a dispatching.

function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {...// Gets the scheduling priority of the Scheduler based on task priority
    const schedulerPriorityLevel = lanePriorityToSchedulerPriority(
      newCallbackPriority,
    );

    // After the scheduling priority is calculated, the Scheduler starts scheduling the React update task
    newCallbackNode = scheduleCallback(
      schedulerPriorityLevel,
      performConcurrentWorkOnRoot.bind(null, root), ); . }Copy the code

LanePriorityToSchedulerPriority calculation is based on task priority scheduling priority process to find the corresponding scheduling priority.

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, ); }}Copy the code

conclusion

There are four priorities mentioned in this paper: event priority, update priority, task priority and scheduling priority, which are in a progressive relationship. The event priority is determined by the event itself. The update priority is calculated by the event and added to root.pendingLanes. The task priority is obtained from the priorities of the most urgent lanes in root.pendingLanes. Several priorities are linked to each other to ensure the priority of high-priority tasks.