“This is the 10th day of my participation in the Gwen Challenge in November. Check out the details: The Last Gwen Challenge in 2021.”

React version: V17.0.3

The root cause of UI interaction is events, which means events are directly related to updates. In React, events have different levels, which leads to updates generated by different events with different priorities. Therefore, the priority of events is the root cause of update priorities. When an update is generated, React generates an update task that is scheduled by the Scheduler.

In React, events are divided into different levels. The ultimate purpose is to determine the priority of task scheduling, so as to achieve incremental rendering of React, prevent frame loss, and achieve a smoother page and better user experience. React therefore has a priority mechanism from events to scheduling.

Event priority

Event priority division

React classifies events into three levels based on their urgency:

  • **DiscreteEventPriority: **Discrete events, such as Click, KeyDown, FocusIn, etc. These events are not triggered continuously and have the highest priority

  • ContinuousEventPriority: ** Block events such as Drag, Mousemove, Scroll, etc. These events are characterized by continuous triggering, block rendering, and have a moderate priority

  • **DefaultEventPriority: ** Events such as load and animation have the lowest priority

Event priorities are classified as follows:

// packages/react-dom/src/events/ReactDOMEventListener.js export function getEventPriority(domEventName: DOMEventName): * { switch (domEventName) { // Used by SimpleEventPlugin: case 'cancel': case 'click': case 'close': case 'contextmenu': case 'copy': case 'cut': case 'auxclick': case 'dblclick': case 'dragend': case 'dragstart': case 'drop': case 'focusin': case 'focusout': case 'input': case 'invalid': case 'keydown': case 'keypress': case 'keyup': case 'mousedown': case 'mouseup': case 'paste': case 'pause': case 'play': case 'pointercancel': case 'pointerdown': case 'pointerup': case 'ratechange': case 'reset': case 'resize': case 'seeked': case 'submit': case 'touchcancel': case 'touchend': case 'touchstart': case 'volumechange': // Used by polyfills: // eslint-disable-next-line no-fallthrough case 'change': case 'selectionchange': case 'textInput': case 'compositionstart': case 'compositionend': case 'compositionupdate': // Only enableCreateEventHandleAPI: // eslint-disable-next-line no-fallthrough case 'beforeblur': case 'afterblur': // Not used by React but could be by user code: // eslint-disable-next-line no-fallthrough case 'beforeinput': case 'blur': case 'fullscreenchange': case 'focus': case 'hashchange': case 'popstate': case 'select': case 'selectstart': return DiscreteEventPriority; case 'drag': case 'dragenter': case 'dragexit': case 'dragleave': case 'dragover': case 'mousemove': case 'mouseout': case 'mouseover': case 'pointermove': case 'pointerout': case 'pointerover': case 'scroll': case 'toggle': case 'touchmove': case 'wheel': // Not used by React but could be by user code: // eslint-disable-next-line no-fallthrough case 'mouseenter': case 'mouseleave': case 'pointerenter': case 'pointerleave': return ContinuousEventPriority; case 'message': { // We might be in the Scheduler callback. // Eventually this mechanism will be replaced by a check // of the current priority on the native scheduler. const schedulerPriority = getCurrentSchedulerPriorityLevel(); switch (schedulerPriority) { case ImmediateSchedulerPriority: return DiscreteEventPriority; case UserBlockingSchedulerPriority: return ContinuousEventPriority; case NormalSchedulerPriority: case LowSchedulerPriority: // TODO: Handle LowSchedulerPriority, somehow. Maybe the same lane as hydration. return DefaultEventPriority; case IdleSchedulerPriority: return IdleEventPriority; default: return DefaultEventPriority; } } default: return DefaultEventPriority; }}Copy the code

Event priority binding

The article “React Source Interpretation of synthetic events” explains the binding of events in detail. When an event is registered in the root DOM container (div#root), the getEventPriority function is called according to the event name to obtain the priority of the current event. Then, according to the priority of the current event, an event listener of different priorities is created, and finally the listener is bound to the root.

A simple process for binding events to root is as follows (see the React source code for compositing events) :

1, the first is createEventListenerWrapperWithPriority function called tectonic event listener:

/ / packages/react - dom/SRC/events/DOMPluginEventSystem js / / 1, tectonic event listener let the listener = createEventListenerWrapperWithPriority( targetContainer, domEventName, eventSystemFlags, );Copy the code

2, and then call getEventPriority function in createEventListenerWrapperWithPriority functions for events priority:

Const eventPriority = getEventPriority(domEventName);Copy the code

3. Then create an event listener corresponding to the event priority

let listenerWrapper; // Switch (eventPriority) {case DiscreteEventPriority: // The event has the highest priority. ListenerWrapper = dispatchDiscreteEvent; break; Case ContinuousEventPriority: // Moderate event priority listenerWrapper = dispatchContinuousEvent; break; Case DefaultEventPriority: // Event with the lowest priority Default: listenerWrapper = dispatchEvent; break; }Copy the code

AddEventListener is called to register the listener with root:

// Return listenerWrapper.bind(null, domEventName, eventSystemFlags, targetContainer,);Copy the code

Update priority

We introduced the creation of hook objects in the article “Use Estate from React Hooks”. It is a linked list with a queue object on each hook. It is also a linked list that stores all updates to the current hook object. These updates are created using the createUpdate constructor. The source code is as follows:

// packages/react-reconciler/src/ReactUpdateQueue.new.js
export function createUpdate(eventTime: number, lane: Lane): Update<*> {
  const update: Update<*> = {
    eventTime,
    lane,

    tag: UpdateState,
    payload: null,
    callback: null,

    next: null,
  };
  return update;
}
Copy the code

In the case of an Update object, its Lane attribute represents its priority, the update priority.

The situation in 4 when creating an UPDATE

In the React system, update objects are created in four situations.

1. Initialize the application

Whether in Legacy or Concurrent mode, the React application starts in updateContainer mode to update root. In the updateContainer function, createUpdate is called to create an update object:

// packages/react-reconciler/src/ReactFiberReconciler.new.js export function updateContainer( element: ReactNodeList, container: OpaqueRoot, parentComponent: ? React$Component<any, any>, callback: ? Function, ): Lane {// remove the Dev section of the code // the current attribute of the container holds a fiber tree during the update, Const current = container. Current; Const eventTime = requestEventTime(); // Create a priority variable const lane = requestUpdateLane(current); Const update = createUpdate(eventTime, lane); const update = createUpdate(eventTime, lane); // Add new update to update list enqueueUpdate(current, update, lane); const root = scheduleUpdateOnFiber(current, lane, eventTime); if (root ! == null) { entangleTransitions(root, current, lane); } return lane; }Copy the code

2. Initiate component updates

When we call setState in a class component, we are essentially calling enqueueSetState. In enqueueSetState, createUpdate is called to create an update object:

// packages/react-reconciler/src/ReactFiberClassComponent.new.js const classComponentUpdater = { isMounted, enqueueSetState(inst, payload, callback) { const fiber = getInstance(inst); const eventTime = requestEventTime(); const lane = requestUpdateLane(fiber); Const update = createUpdate(eventTime, lane); / /... enqueueUpdate(fiber, update, lane); const root = scheduleUpdateOnFiber(fiber, lane, eventTime); if (root ! == null) { entangleTransitions(root, fiber, lane); } / /... } / /... };Copy the code

Update the context

Before updating the context, it first iterates through the fiber linked list from the ContextProvider node, looking for matching consumers from the context dependency list on the fiber. Then call createUpdate to create an update object:

// https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactFiberNewContext.new.js#L189 function propagateContextChange_eager<T>( workInProgress: Fiber, context: ReactContext<T>, renderLanes: Lanes, ): void { // ... let fiber = workInProgress.child; if (fiber ! == null) { // Set the return pointer of the child to the work-in-progress fiber. fiber.return = workInProgress; } while (fiber ! == null) { let nextFiber; // Visit this fiber. const list = fiber.dependencies; if (list ! == null) { nextFiber = fiber.child; let dependency = list.firstContext; while (dependency ! == null) { // Check if the context matches. if (dependency.context === context) { // Match! Schedule an update on this fiber. if (fiber.tag === ClassComponent) { // Schedule a force update on the work-in-progress. const lane = pickArbitraryLane(renderLanes); Const update = createUpdate(NoTimestamp, lane); update.tag = ForceUpdate; / /... } / /... } dependency = dependency.next; } } fiber = nextFiber; }}Copy the code

An error occurred while executing component lifecycle functions

The createUpdate function is also called to create an update object if an error occurs during the execution of a lifecycle function throughout the life of the component:

// packages/react-reconciler/src/ReactFiberThrow.new.js function createRootErrorUpdate( fiber: Fiber, errorInfo: CapturedValue<mixed>, lane: lane,): Update<mixed> {const Update = createUpdate(NoTimestamp, lane); // Unmount the root by rendering null. update.tag = CaptureUpdate; // Caution: React DevTools currently depends on this property // being called "element". update.payload = {element: null}; const error = errorInfo.value; update.callback = () => { onUncaughtError(error); logCapturedError(fiber, errorInfo); }; return update; } function createClassErrorUpdate( fiber: Fiber, errorInfo: CapturedValue<mixed>, lane: Lane, ): Update<mixed> {// createUpdate object const Update = createUpdate(NoTimestamp, lane); update.tag = CaptureUpdate; / /... return update; }Copy the code

Of the above four cases, only “application initialization” and “initiate component update” create update.lane logic is the same, both create an UPDATE priority based on the current time. In the remaining two cases, the UPDATE priority is passed in externally.

Here, we focus on the logic of update.Lane created in the case of “application initialization” and “initiating component updates.” Before creating the update object, they all call the requestUpdateLane function to create the update priority.

Gets the update priority

// react-reconciler/src/ReactFiberWorkLoop.new.js export function requestUpdateLane(fiber: Fiber): Lane {// Special cases // The mode attribute value on the fiber node comes from the mode of HostFiberRoot object // Marks which startup mode (Legacy mode, Concurrent mode) const mode belongs to  = fiber.mode; // The startup mode is not Concurrent, If ((mode & ConcurrentMode) === NoMode) {return (SyncLane: Lane); } else if ( ! deferRenderPhaseUpdateToNextBatch && (executionContext & RenderContext) ! == NoContext && workInProgressRootRenderLanes ! == NoLanes ) { // This is a render phase update. These are not officially supported. The // old behavior is to give this  the same "thread" (lanes) as // whatever is currently rendering. So if you call `setState` on a component // that happens later in the same render, it will flush. Ideally, we want to // remove the special case and treat them as if they came from an // interleaved event. Regardless, this pattern is not officially supported. // This behavior is only a fallback. The flag only exists until we can roll //  out the setState warning, Since existing code might be forced on // the current behavior. PickArbitraryLane returns a lane with a high priority. PickArbitraryLane returns a lane with a high priority pickArbitraryLane(workInProgressRootRenderLanes); } const isTransition = requestCurrentTransition()! == NoTransition; if (isTransition) { if ( __DEV__ && warnOnSubscriptionInsideStartTransition && ReactCurrentBatchConfig._updatedFibers ) Reactcurrentbatchconfig. _updatedFibers. Add (fiber); } // The algorithm for assigning an update to a lane should be stable for all // updates at the same priority within the  same event. To do this, the // inputs to the algorithm must be the same. // // The trick we use is to cache the first of each of these inputs within an // event. Then reset the cached values once we can be sure the event is // over. Our heuristic for that is whenever we enter a concurrent work loop. if (currentEventTransitionLane === NoLane) { // All transitions within the Same event are assigned the same lane. / / by transition distribution rules of transition priority priority currentEventTransitionLane = claimNextTransitionLane (); } return currentEventTransitionLane; * / originating inside certain React methods, like flushSync, have // their priority set by tracking it with a context variable. // // The opaque type returned by the host config is internally a lane, so we can // use that directly. // TODO: Move this type conversion to the event priority module. const updateLane: Lane = (getCurrentUpdatePriority(): any); if (updateLane ! == NoLane) { return updateLane; } // This update originated outside React. Ask the host environment for an appropriate priority, based on the type of event. // // The opaque type returned by the host config is internally a lane, so we can // use that directly. // TODO: Move this type conversion to the event priority module. const eventLane: Lane = (getCurrentEventPriority(): any); return eventLane; }Copy the code

The requestUpdateLane function returns an appropriate update priority.

1. Determine the priority of the update based on the startup mode

  • If the boot mode is Legacy, the update priority is SyncLane, which is the synchronization priority.

  • If the startup mode is Concurrent, the pickArbitraryLane function is called to return the lane performing the task, which is a high priority.

    const mode = fiber.mode; // The startup mode is not Concurrent, If ((mode & ConcurrentMode) === NoMode) {return (SyncLane: Lane); } else if ( ! deferRenderPhaseUpdateToNextBatch && (executionContext & RenderContext) ! == NoContext && workInProgressRootRenderLanes ! == NoLanes) {// Start in Concurrent mode, Returns the currently executing task of lane / / the new task and existing task for a batch update / / pickArbitraryLane () function returns the lane priority is high priority / / workInProgressRootRenderLanes is a global variable, Storage of the root node of the current working priority return pickArbitraryLane (workInProgressRootRenderLanes); }

2. In suspense, assign transition priorities according to transition priority allocation rules.

When a priority is assigned, it starts from the right of the transition priority. Subsequent tasks move one bit to the left until the last position is assigned. Subsequent tasks start from the first position to the right

Const isTransition = requestCurrentTransition()! == NoTransition; if (isTransition) { // ... if (currentEventTransitionLane === NoLane) { // All transitions within the same event are assigned the same lane. // Through interim distribution rules of priority priority currentEventTransitionLane = claimNextTransitionLane (); } return currentEventTransitionLane; }Copy the code

Call getCurrentUpdatePriority to get the priority of the current update task:

Const updateLane: Lane = (getCurrentUpdatePriority(): any); if (updateLane ! == NoLane) { return updateLane; }Copy the code

getCurrentUpdatePriority

// packages/react-reconciler/src/ReactEventPriorities.new.js


let currentUpdatePriority: EventPriority = NoLane;

export function getCurrentUpdatePriority(): EventPriority {
  return currentUpdatePriority;
}
Copy the code

4. Call getCurrentEventPriority to get the priority of the current event according to the type of the current event:

Const eventLane: Lane = (getCurrentEventPriority(): any); return eventLane;Copy the code

In both cases of “application initialization” and “initiating component updates”, after obtaining the update priority from requestUpdateLane, an Update object is created based on the current update priority. Finally, scheduleUpdateOnFiber(current, Lane, eventTime) is used to bring update.lane into the scheduling stage.

We continue to track where this Update priority is going.

Update priority direction

In scheduleUpdateOnFiber, the update will be priority markUpdateLaneFromFiberToRoot after binary arithmetic is added to the lanes of the fiber node attributes.

// packages/react-reconciler/src/ReactFiberWorkLoop.new.js function markUpdateLaneFromFiberToRoot( sourceFiber: Fiber, lane: Lane, ): FiberRoot | null {/ / Update the source fiber 's lanes / / Update the lane priority sourceFiber lanes = mergeLanes (sourceFiber lanes, lane); let alternate = sourceFiber.alternate; if (alternate ! == null) { alternate.lanes = mergeLanes(alternate.lanes, lane); } / /... // Walk the parent path to the root and update the child lanes. let parent = sourceFiber.return; while (parent ! == null) { parent.childLanes = mergeLanes(parent.childLanes, lane); alternate = parent.alternate; if (alternate ! == null) { alternate.childLanes = mergeLanes(alternate.childLanes, lane); } else { /// ... } node = parent; parent = parent.return; } if (node.tag === HostRoot) { const root: FiberRoot = node.stateNode; return root; } else { return null; }}Copy the code

In addition to adding the Update priority to the Lanes attribute of the Fiber node, the Update priority is also added to the pendingLanes attribute of root via markRootUpdated, marking root that there is an UPDATE waiting on it.

// packages/react-reconciler/src/ReactFiberLane.new.js export function markRootUpdated( root: FiberRoot, updateLane: Lane, eventTime: number, ) { root.pendingLanes |= updateLane; // If there are any suspended transitions, it's possible this new update // could unblock them. Clear the suspended lanes so that we can try rendering // them again. // // TODO: We really only need to unsuspend only lanes that are in the // `subtreeLanes` of the updated fiber, or the update lanes of the return // path. This would exclude suspended updates in an unrelated sibling tree, // since there's no way for this update to unblock it. // // We don't do this if the incoming update is idle, because we never process // idle updates until after all the regular updates have finished; there's no // way it could unblock a transition. if (updateLane ! == IdleLane) { root.suspendedLanes = NoLanes; root.pingedLanes = NoLanes; } const eventTimes = root.eventTimes; const index = laneToIndex(updateLane); // We can always overwrite an existing timestamp because we prefer the most // recent event, and we assume time is monotonically increasing. eventTimes[index] = eventTime; }Copy the code

The update priority added to root in markRootUpdated will be used to calculate the task priority in the task priority calculation.

In scheduleUpdateOnFiber, update priority will also be added to the global variable workInProgressRootUpdatedLanes.

if (
    deferRenderPhaseUpdateToNextBatch ||
    (executionContext & RenderContext) === NoContext
  ) {
    workInProgressRootUpdatedLanes = mergeLanes(
      workInProgressRootUpdatedLanes,
      lane,
    );
  }
Copy the code

Task priority

A React update task with a lane priority is executed by a React update task. The task priority is used to distinguish the urgency of multiple update tasks, and the one with the highest priority is processed first. Task priority is calculated based on update priority.

Let’s assume that two updates are currently 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 equal to the task priority of the former, Scheduler will not cancel the task scheduling of the former, but reuse the update task of the former, and merge two updates of the same priority into one task. If the latter has a higher priority than the former, the Scheduler suspends or cancels the former in favor of the higher-priority latter. If the task priority of the latter is lower than that of the former, the scheduling of the former will not be cancelled by the Scheduler, but after the update of the former, the Scheduler will be called again to initiate a task scheduling for the latter.

Let’s explain the above conclusion in code.

1. Task priority calculation

The priority of a task is one that is to be evaluated before the task is scheduled, with the code logic being one ensureRootIsScheduled:

/ / packages/react - the reconciler/SRC/ReactFiberWorkLoop. New. Js / / use this function to arrange tasks for the root, each root only one task / / each update will call this function, And call this function before exiting the task. function ensureRootIsScheduled(root: FiberRoot, currentTime: number) { //... Const nextLanes = getNextLanes(root, root === workInProgressRoot? workInProgressRootRenderLanes : NoLanes, ); / /... // We use the highest priority lane to represent the priority of the callback. Used to indicate the Scheduler. ScheduleCallback returns the node priority const newCallbackPriority = getHighestPriorityLane (nextLanes); / /... }Copy the code

Under ensureRootIsScheduled, getNextLanes is called to calculate the batch of Lanes (nextLanes) that should be processed in this update, thus determining the next lane lane to be operated with and its priority.

Task priority calculation works like this: After the lanes (expiredLanes, suspendedLanes, pingedLanes, etc.) stored on the root object are processed by getNextLanes, the lanes that need to be processed urgently are selected. Then pass these lanes into getHighestPriorityLane and find the priority of these lanes as the task priority.

2. Reuse existing update tasks

// Use this function to schedule tasks for root, each root has only one task // This function is called every update, and is called before exiting the task. function ensureRootIsScheduled(root: FiberRoot, currentTime: number) { //... // Check if there's an existing task. We may be able to reuse it. We might be able to reuse it. const existingCallbackPriority = root.callbackPriority; If (// The priority of the existing rendering task is the same as the priority of the next rendering task, // the existing rendering task is reused, ExistingCallbackPriority === newCallbackPriority && // Special case related to 'act'.if the currently scheduled task is a // Scheduler task, rather than an `act` task, cancel it and re-scheduled // on the `act` queue. ! ( __DEV__ && ReactCurrentActQueue.current ! == null && existingCallbackNode ! == fakeActCallbackNode)) {The priority hasn't changed. We can reuse The existing task. Exit. Reuse the existing task return; } / /... }Copy the code

As you can see from the above code, if the priority of the first and second update is the same, return and no further code is executed. In other words, Scheduler does not cancel the task scheduling of the previous update when the priority of the previous update is the same, but reuses the update task of the former and combines the two updates of the same priority into one task.

If the priorities of the two updates are different, the code following the return is executed to initiate a new task scheduling.

Scheduling priority

In the React system, only concurrent tasks are scheduled by the Scheduler. In Scheduler, this task is wrapped by unstable_scheduleCallback to generate a task that is Scheduler’s own. The priority of this task is the scheduling priority. This scheduling priority is calculated from the event priority and task priority.

Scheduling priority definition

// packages/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

Scheduling priority calculation

// Use this function to schedule tasks for root, each root has only one task // This function is called every update, and is called before exiting the task. function ensureRootIsScheduled(root: FiberRoot, currentTime: number) { //... If (newCallbackPriority === SyncLane) {//... } else {// else logic processes concurrent tasks, which need to go through Scheduler // let schedulerPriorityLevel; Switch (lanesToEventPriority(nextLanes)) {// DiscreteEventPriority has the highest priority. Immediately give a mission (top) priority case DiscreteEventPriority: schedulerPriorityLevel = ImmediateSchedulerPriority; break; // ContinuousEventPriority: Specifies the priority of the task to be blocked. Case ContinuousEventPriority: schedulerPriorityLevel = UserBlockingSchedulerPriority; break; Case DefaultEventPriority: schedulerPriorityLevel = NormalSchedulerPriority; break; Case IdleEventPriority: schedulerPriorityLevel = IdleSchedulerPriority; break; Default: // Normal schedulerPriorityLevel = NormalSchedulerPriority; break; } // According to scheduling priority, Scheduling concurrent tasks newCallbackNode = scheduleCallback (schedulerPriorityLevel performConcurrentWorkOnRoot. Bind (null, root),); } / /... }Copy the code

In the above code (else statement module), we define a scheduler priority variable called schedulerPriorityLevel, and pass the task priority calculated by getNextLanes to lanesToEventPriority(nextLanes). Obtain the event priorities of nextLanes and assign corresponding scheduling priorities based on the event priorities of nextLanes.

Let’s look at how lanesToEventPriority gets the event priority corresponding to nextLanes.

// packages/react-reconciler/src/ReactEventPriorities.new.js export function isHigherEventPriority( a: EventPriority, b: EventPriority, ): boolean { return a ! == 0 && a < b; } export function lanesToEventPriority(lanes: lanes): EventPriority { const lane = getHighestPriorityLane(lanes); if (! isHigherEventPriority(DiscreteEventPriority, lane)) { return DiscreteEventPriority; } if (! isHigherEventPriority(ContinuousEventPriority, lane)) { return ContinuousEventPriority; } if (includesNonIdleWork(lane)) { return DefaultEventPriority; } return IdleEventPriority; }Copy the code

In lanesToEventPriority, the function getHighestPriorityLane is first called to obtain the lane with the highest priority in nextLanes. Then call isHigherEventPriority and includesNonIdleWork respectively to determine the event priority to which the lane with the highest priority belongs.

Scheduler wraps tasks

When a concurrent task is scheduled by the Scheduler, it is wrapped by the Scheduler as its own task. This part of the processing logic is in Scheduler’s unstable_scheduleCallback function.

// packages/scheduler/src/forks/Scheduler.js function unstable_scheduleCallback(priorityLevel, callback, Var currentTime = getCurrentTime(); // task startTime 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; } // Set the corresponding time based on the scheduling priority 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; // A Scheduler's own task var newTask = {id: TaskIdCounter++, callback, / / the callback is a function performConcurrentWorkOnRoot priorityLevel, / / scheduling priority startTime, // Task expirationTime sortIndex: -1, // For task priority}; if (enableProfiling) { newTask.isQueued = false; } // Delay time is passed to the Scheduler to schedule the task, startTime is greater than currentTime, If (startTime > currentTime) {// This is a delayed task.newtask.sortIndex = startTime; TimerQueue (timerQueue, newTask); timerQueue (timerQueue, newTask); // peek looks at the apex of the heap, If (peek(taskQueue) === null && newTask === peek(timerQueue)) {// All tasks are delayed, // This is the earliest delay of the project. // This is the earliest delay of the project // Cancel the existing timer cancelHostTimeout(); } else { isHostTimeoutScheduled = true; } // Schedule a timeout. // Schedule a timer requestHostTimeout(handleTimeout, startTime-currentTime); } } else { newTask.sortIndex = expirationTime; // taskQueue is a binary heap structure that stores task push(taskQueue, newTask) as the smallest heap; if (enableProfiling) { markTaskStart(newTask, currentTime); newTask.isQueued = true; } // Schedule a host callback, if needed. If we're already performing work, // wait until the next time we yield. if (! isHostCallbackScheduled && ! isPerformingWork) { isHostCallbackScheduled = true; requestHostCallback(flushWork); } } return newTask; }Copy the code

As you can see in unstable_scheduleCallback:

1. Obtain the performance. Now () timestamp and calculate the startTIme of the task.

Var currentTime = getCurrentTime(); // task startTime 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; }Copy the code

2. Set a expirationTime based on the scheduling priority. Based on the expirationTime and startTime, calculate the expirationTime of a task.

Var timeout; // Set the corresponding time based on the scheduling priority. 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;Copy the code

3, then create a Scheduler own task, the callback is a function performConcurrentWorkOnRoot on this task.

// A Scheduler's own task var newTask = {id: TaskIdCounter++, callback, / / the callback is a function performConcurrentWorkOnRoot priorityLevel, / / scheduling priority startTime, // task expirationTime sortIndex: -1, // compare two tasks when heap sort}; if (enableProfiling) { newTask.isQueued = false; }Copy the code

4. Then store the task in the smallest heap timerQueue or taskQueue, so that the task with the highest priority can be retrieved in O(1) time complexity.

// Delay time is passed when Scheduler schedules tasks. StartTime is greater than currentTime. If (startTime > currentTime) {// This is a delayed task.newtask.sortIndex = startTime; TimerQueue (timerQueue, newTask); timerQueue (timerQueue, newTask); // peek looks at the apex of the heap, If (peek(taskQueue) === null && newTask === peek(timerQueue)) {// All tasks are delayed, // This is the earliest delay of the project. // This is the earliest delay of the project // Cancel the existing timer cancelHostTimeout(); } else { isHostTimeoutScheduled = true; } // Schedule a timeout. // Schedule a timer requestHostTimeout(handleTimeout, startTime-currentTime); } } else { newTask.sortIndex = expirationTime; // taskQueue is a binary heap structure that stores task push(taskQueue, newTask) as the smallest heap; if (enableProfiling) { markTaskStart(newTask, currentTime); newTask.isQueued = true; } // Schedule a host callback, if needed. If we're already performing work, // wait until the next time we yield. if (! isHostCallbackScheduled && ! isPerformingWork) { isHostCallbackScheduled = true; requestHostCallback(flushWork); }}Copy the code

For details on how to store and obtain tasks, see heap Sorting with the React algorithm.

Conversion relationship

Event priority, update priority, task priority and scheduling priority are in a progressive relationship. Their relationship is shown below:

1. The event priority is the root of the update priority, which is converted from the event priority by calling getCurrentUpdatePriority.

// packages/react-reconciler/src/ReactEventPriorities.new.js
export const DiscreteEventPriority: EventPriority = SyncLane;
export const ContinuousEventPriority: EventPriority = InputContinuousLane;
export const DefaultEventPriority: EventPriority = DefaultLane;
export const IdleEventPriority: EventPriority = IdleLane;

let currentUpdatePriority: EventPriority = NoLane;

export function getCurrentUpdatePriority(): EventPriority {
  return currentUpdatePriority;
}
Copy the code

2. Task priority is used to distinguish the urgency of multiple update tasks. Whoever has a higher priority will be dealt with first. Task priority is computed by calling getHighestPriorityLanes, passing in the update priority.

// packages/react-reconciler/src/ReactFiberWorkLoop.new.js function getHighestPriorityLanes(lanes: Lanes | Lane): Lanes { switch (getHighestPriorityLane(lanes)) { case SyncLane: return SyncLane; case InputContinuousHydrationLane: return InputContinuousHydrationLane; case InputContinuousLane: return InputContinuousLane; case DefaultHydrationLane: return DefaultHydrationLane; case DefaultLane: return DefaultLane; case TransitionHydrationLane: return TransitionHydrationLane; case TransitionLane1: case TransitionLane2: case TransitionLane3: case TransitionLane4: case TransitionLane5: case TransitionLane6: case TransitionLane7: case TransitionLane8: case TransitionLane9: case TransitionLane10: case TransitionLane11: case TransitionLane12: case TransitionLane13: case TransitionLane14: case TransitionLane15: case TransitionLane16: return lanes & TransitionLanes; case RetryLane1: case RetryLane2: case RetryLane3: case RetryLane4: case RetryLane5: return lanes & RetryLanes; case SelectiveHydrationLane: return SelectiveHydrationLane; case IdleHydrationLane: return IdleHydrationLane; case IdleLane: return IdleLane; case OffscreenLane: return OffscreenLane; default: if (__DEV__) { console.error( 'Should have found matching lanes. This is a bug in React.', ); } // This shouldn't be reachable, but as a fallback, return the entire bitmask. return lanes; }}Copy the code

3. Scheduling priority is the priority mechanism of Scheduler, which is calculated by task priority and event priority. The event priority is calculated from the task priority first, and then the scheduling priority is calculated from the event priority.

/ / packages/react - the reconciler/SRC/ReactEventPriorities. New. The lanes are the task priority/js / / parameter/task priority into event priority export function lanesToEventPriority(lanes: Lanes): EventPriority { const lane = getHighestPriorityLane(lanes); if (! isHigherEventPriority(DiscreteEventPriority, lane)) { return DiscreteEventPriority; } if (! isHigherEventPriority(ContinuousEventPriority, lane)) { return ContinuousEventPriority; } if (includesNonIdleWork(lane)) { return DefaultEventPriority; } return IdleEventPriority; } / / packages/react - the reconciler/SRC/ReactFiberWorkLoop. New. Js / / scheduling priority class let schedulerPriorityLevel; LanesToEventPriority (nextLanes) returns the event priority (lanesToEventPriority(nextLanes)) {// DiscreteEventPriority Has the highest priority for discrete events, which indicates the highest priority for immediate execution of the task. Case DiscreteEventPriority: schedulerPriorityLevel = ImmediateSchedulerPriority; break; // ContinuousEventPriority: Specifies the priority of the task to be blocked. Case ContinuousEventPriority: schedulerPriorityLevel = UserBlockingSchedulerPriority; break; Case DefaultEventPriority: schedulerPriorityLevel = NormalSchedulerPriority; break; Case IdleEventPriority: schedulerPriorityLevel = IdleSchedulerPriority; break; Default: // Normal schedulerPriorityLevel = NormalSchedulerPriority; break; }Copy the code

conclusion

  1. This article introduces four priorities in React: event priority, update priority, task priority, and scheduling priority.

  2. The event priority is determined by the event itself. According to the emergency of the event, there are three levels of DiscreteEventPriority, ContinuousEventPriority, and DefaultEventPriority.

  3. The update priority is converted from the event priority and then placed on the Fiber node’s Lanes property and root’s pendingLanes property.

  4. The task priority is calculated from the update priority. It is stored in lanes (expiredLanes, suspendedLanes, pingedLanes, etc.) on the root object. After processing getNextLanes and getHighestPriorityLane, find out the priorities of these Lanes and use them as task priorities.

  5. The scheduling priority is calculated by task priority and event priority. The event priority is calculated from the task priority first, and then the scheduling priority is calculated from the event priority.

  6. React implements interruptible rendering, time slicing, and suspense ** through flexible use of priorities.

Reference Documents:

Segmentfault.com/a/119000003…

Juejin. Cn/post / 699313…