1. The main function is time slice and priority scheduling

1. Time slice

The essence of time slicing is to simulate the implementation of requestIdleCallback

The browser performs one frame of tasks, and four other tasks besides the browser rearrangement/redraw can be used to execute our JS

Only requestAnimationFrame is determined to be executed before the browser rearranges/redraws

One task(macro task) — all jobs (micro tasks) in the queue — requestAnimationFrame — browser rearrange/redraw — requestIdleCallback

Our simulated implementation of requestIdleCallback cannot be called at a certain time either

So, the next best thing is that Scheduler’s time-slicing functionality is implemented through tasks (macro tasks).

If you have a Messagechannel you implement it using messagechannel, if you don’t use setTimeout

So Scheduler executes callback functions that need to be executed as callbacks to the MessageChannel. If the current host environment does not support MessageChannel, use setTimeout.

So the time slice callback is executed in messagechannel otherwise in the setTimeout callback

ShouldYieldToHost should be false and the workloop should be interrupted until the macro task is executed in the next frame

2. Priority scheduling

Schedule is independent of the React package, and its priority is independent of the React priority. How is schedule connected to the React priority?

1. Priority scheduling

Because Schedule exposes an unstable_runWithPriority method, this function

The unstable_runWithPriority(priorityLevel, eventHandler) action causes the eventHandler function to be executed with priorityLevel priority

Schedule has five priorities

case ImmediatePriority:
    case UserBlockingPriority:
    case NormalPriority:
    case LowPriority:
    case IdlePriority:
      break;
    default:
      priorityLevel = NormalPriority;
Copy the code

CommitRoot: Executes the commitRootImpl function synchronously

runWithPriority(
    ImmediateSchedulerPriority,/ / priority
    commitRootImpl.bind(null, root, renderPriorityLevel),
  );
Copy the code

2. Significance of priority

The priority corresponds to the expiration time. Sorting by time is priority sorting

var timeout;/ /! Different priorities The lower the priority, the larger the timeout
  switch (priorityLevel) {
    case ImmediatePriority:
      timeout = IMMEDIATE_PRIORITY_TIMEOUT;/ / 1
      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;/ /! Start time +timeout = Expiration time
Copy the code

This method is used to expose the scheduleCallback function to apriorityRegister the callback function.

if(! rootDoesHavePassiveEffects) { rootDoesHavePassiveEffects =true;
  scheduleCallback(NormalSchedulerPriority, () = > {
    flushPassiveEffects();//useEffect is executed
    return null;// Register flushPassiveEffects with NormalSchedulerPriority. Create tasks and add scheduling to the minimum heap until the time to execute
  });
}
Copy the code

Execute the process

newCallbackNode = scheduleCallback(
      schedulerPriorityLevel,
      performConcurrentWorkOnRoot.bind(null, root),
    );
Copy the code

This function is registered with schedulerPriorityLevel priority, and while the function is executing, if the interrupt returns the same value, the callback of the new task will continue to execute until it returns null

Is that why the Render phase is interruptible for recovery

3. How the React internal Lane model works

It has three features: 1. Different priorities 2. Batch 3. It’s easy to calculate

/ /! Different tracks the lower the priority of lanes is the batch
export const NoLanes: Lanes = / * * / 0b0000000000000000000000000000000;
export const NoLane: Lane = / * * / 0b0000000000000000000000000000000;

export const SyncLane: Lane = / * * / 0b0000000000000000000000000000001;
export const SyncBatchedLane: Lane = / * * / 0b0000000000000000000000000000010;

export const InputDiscreteHydrationLane: Lane = / * * / 0b0000000000000000000000000000100;
const InputDiscreteLanes: Lanes = / * * / 0b0000000000000000000000000011000;

const InputContinuousHydrationLane: Lane = / * * / 0b0000000000000000000000000100000;
const InputContinuousLanes: Lanes = / * * / 0b0000000000000000000000011000000;

export const DefaultHydrationLane: Lane = / * * / 0b0000000000000000000000100000000; 
export const DefaultLanes: Lanes = / * * / 0b0000000000000000000111000000000;/ /! All three tracks have the same priority batch concept

const TransitionHydrationLane: Lane = / * * / 0b0000000000000000001000000000000;
const TransitionLanes: Lanes = / * * / 0b0000000001111111110000000000000;/ /! The lower priority tracks are the more batch they are because they are more likely to be interrupted and left behind

const RetryLanes: Lanes = / * * / 0b0000011110000000000000000000000;

export const SomeRetryLane: Lanes = / * * / 0b0000010000000000000000000000000;

export const SelectiveHydrationLane: Lane = / * * / 0b0000100000000000000000000000000;

const NonIdleLanes = / * * / 0b0000111111111111111111111111111;

export const IdleHydrationLane: Lane = / * * / 0b0001000000000000000000000000000;
const IdleLanes: Lanes = / * * / 0b0110000000000000000000000000000;
Copy the code

4. Asynchronous interruptible updates

React combines the lane and Schedule processes

1. Find the priority of schedule based on the mode

2. Switch to the priority of lane

3. Find lanes that have the highest priority and are not occupied.

4. Assign lane to update.lane

5. Then merge the current fiber. Lanes into this lane and all the parent childlanes into this lane

6. Add root’s PendingLanes (lanes that need to be executed on root) to this lane

7. Get the callbacknode attribute of root, which is the callback function of the currently executing task of root, if any

Select * from ‘root’ where ‘lane’ = ‘timestamp’; select * from ‘root’ where ‘lane’ = ‘timestamp’; The lower the priority is, the higher the timestamp is. Therefore, all PendingLanes have an expiration time. If the current time exceeds timestamp+ startTime (a task has expired), Add this lane to root.expiredLanes (expiredLanes on root) to solve the hunger problem

If there are any expiredLanes on root, we will get expiredLanes and pendinglanes. If there are any expiredLanes on root, we will get expiredLanes and pendinglanes first. Nextlanes indicates the lanes with the highest priority under root

10. If callbacknode exists, we determine whether NextLanes or callbacknode has the highest priority. If nextlanes is higher, the task will be canceled

11. The lane priority back the schedule for priority, and then call schedulecallback incoming performConcurrentworkOnroot callback function, and this function if it is time to get to interrupt will be returned to the function, Determine within schedule that if a function is returned and the function continues as a callback to NewTask that is interruptible recovery

 if (root.callbackNode === originalCallbackNode) {/ / package time and other high-priority performConcurrentworkOnroot will go this because the callback function the same task is this, the priority in the schedule to save the priority this function
    // The task node scheduled for this root is the same one that's
    // currently executed. Need to return a continuation.
    return performConcurrentWorkOnRoot.bind(null, root);
  }
return null;
Copy the code

Callback = currenttask.callback (currenttask.callback);

 currentTask.callback = continuationCallback;/ /! Continue to treat this function as a new task
        markTaskYield(currentTask, currentTime);
Copy the code

Root. CallbackNode callback function is the root of the task, originalCallbackNode is performConcurrentworkOnroot, In here is to determine if the new function is not originalCallbackNode, so is the higher priority task in la, so returns Null, don’t let performConcurrentworkOnroot as the next task, the task will pop. If it is equal, it is an update of the time slice because the function has not changed, so return the function to let the next task continue.

12. Whether the time slice times out according to the current time – task start time >5ms? yieldInterval = Math.floor(1000 / fps);

5ms is based on FPS

13. com mitRoot phase: Root. finishedLanes = ‘remainingLanes’; root.finishedLanes =’ remainingLanes’ Penginglanes, we initialized them to empty for next use, update pengingLanes to RemainingLanes

5.batchupdates

Previous versions of batchedUpdates

export function batchedUpdates<A.R> (fn: A => R, a: A) :R {
  const prevExecutionContext = executionContext;
  executionContext |= BatchedContext;/ /! This function is similar to runwithPriority executing fn in a context including BatchedContext and then resetting the context
  try {
    return fn(a);FlushSyncCallbackQueue = flushSyncCallbackQueue = flushSyncCallbackQueue = flushSyncCallbackQueue
  } finally {
    executionContext = prevExecutionContext;
    if (executionContext === NoContext) {
      // Flush the immediate callbacks that were scheduled during this batchresetRenderTimer(); flushSyncCallbackQueue(); }}}Copy the code

The downside is that if onclick is setState asynchronous, there is no BatchedContext and you still have to render the same number of times.

The current version of batchedUpdates

The Lane model addresses this shortcoming by enabling ConcurrentMode

/ / for
 handleClick(){
    setTimeout(() = > {
      this.setState({
        number:1
      })
      this.setState({
        number:2})},0);
  }
Copy the code

So every time setState is going to get lane, requestUpdateLane,

There are two key things about this function

if (currentEventWipLanes === NoLanes) {
    currentEventWipLanes = workInProgressRootIncludedLanes;
  }
Copy the code

Behind the first setState currentEventWipLanes, setState currentEventWipLanes has value and is equal to the workInProgressRootIncludedLanes

 lane = findUpdateLane(schedulerLanePriority, currentEventWipLanes);
Copy the code

SchedulerLanePriority and currentEventWipLanes are the same. The same goes for lane, which is the lane with the highest priority of lanes corresponding to the schedulerLanePriority after removing currentEventWipLanes.

Finally, there is a judgment that executes not render

 if(existingCallbackNode ! = =null) {
    var existingCallbackPriority = root.callbackPriority;

    if (existingCallbackPriority === newCallbackPriority) {
      // The priority hasn't changed. We can reuse the existing task. Exit.
      return;
    } 
Copy the code

The first time setState, existingCallbackNode is null

The second setState existingCallbackNode is not null, and the two lanes are the same, so existingCallbackPriority === newCallbackPriority the priority of the two tasks is the same, Therefore, setState will not schedule the render phase, so only the first setState implementation batch will be executed.

Principle:

Only began to enter the render phase, workInProgressRootIncludedLanes = lanes; WorkInProgressRootIncludedLanes will be assigned for the render of the lanes. So at the same time workInProgressRootIncludedLanes same, after render phase workInProgressRootIncludedLanes for other values will be assigned a value. Implemented a render phase batch.

soThe overall logical:

Every time into the render phase, workInProgressRootIncludedLanes will be assigned for the lanes, so the same onclick settimeout function, only the first entered the stage of render setState scheduling, And the setState of synchronization is going to be because

WorkInProgressRootIncludedLanes (same as is done with the last workInProgressRootIncludedLanes render phase, result in the same lane, without adding render phase scheduling. So after the render execution workInProgressRootIncludedLanes assignment for this lane, the next execution setState can have different workInProgressRootIncludedLanes, Can enter render scheduling. Although the render phase is scheduled only once, updates are created and queued, so render only once executes two updates in sequence, so the value is the last new value.

Mainly workInProgressRootIncludedLanes this variable.

6. How to cut in line for high-priority updates

The low-priority lane is 10 to perform the Render phase of schecallback scheduling

If the lane obtained by the high priority (click the event) is 8, it will judge whether the current ExitCallback exists (whether any task is scheduled). If it does, it will judge the two priorities. If the new high is found, the old task will be cancelled, that is, the low priority task will be cancelled. Then the Render phase is scheduled with a priority of Lane =8 to implement queue jumping.

How do I remove the impact of low-priority updates? Entering the Render phase there is a function prepareFreshStak:

If the lane of the new render is inconsistent with the lane of the render phase that is already executing, prepareFreshStack will be executed. This function clears the last workingFiber tree and

Reset some of the properties, and workInProgressRootIncludedLanes = new lanes. The main thing is to clean up the previously created Fiber. Re-create a new WorkingFiber to diff.

  }
  workInProgressRoot = root;
  workInProgress = createWorkInProgress(root.current, null);// Clear the previous workingFiber tree
  workInProgressRootRenderLanes = subtreeRenderLanes = workInProgressRootIncludedLanes = lanes;
  workInProgressRootExitStatus = RootIncomplete;
  workInProgressRootFatalError = null;
  workInProgressRootSkippedLanes = NoLanes;
  workInProgressRootUpdatedLanes = NoLanes;
  workInProgressRootPingedLanes = NoLanes;
Copy the code

Different from the fifth difference: if the lane is the same, the same logic as batchupdates is returned directly and the second time is not scheduled

All component updates are still in Fiber. It doesn’t matter what priority the render phase takes, it doesn’t matter what priority the render phase takes, it doesn’t matter what priority the render phase takes.

7. The realization of suspense

The solution to BOTTLENECKS in I/O is to solve time problems for asynchronous requests, and CPU problems are solved with asynchronous interruptable updates.

import React, { Suspense, useState, unstable_useTransition as useTransition } from "react"; import { wrapPromise } from "./utils"; function fetchTime() { return wrapPromise( new Promise((resolve) => { setTimeout(() => { resolve({ time: new Date().toLocaleString() }); }, 1000); })); } function Clock({ resource }) { const { time } = resource.read(); return <h3>{time}</h3>; } function Button({ onClick, children }) { const [startTransition, isPending] = useTransition({ timeoutMs: 2000 }); const btnOnClick = () => { startTransition(() => { onClick(); // Also 2000ms delay lane and setpending:false consistent priority}); }; return ( <> <button disabled={isPending} onClick={btnOnClick}> {children} </button> <span>{isPending && " loading"}</span> </> ); } export default function App() { const [time, setTime] = useState(fetchTime()); const load = () => { setTime(fetchTime()); }; return ( <Suspense fallback={<div>Loading... </div>}> <Button onClick={load}> load </Button> <Clock resource={time} /> </Suspense>); }Copy the code

UseTransition implementation

Lian updates will be triggered twice, one of which is the update with high priority and the callback function is pending=true, indicating that we are executing the delayed task execution and waiting for pending, and the second is the update with low priority. The callback function is setPending =false. Callback (the function we passed in useTransition), low-priority execution of pending=false and any callbacks we want to postpone. This enables the callback function to be delayed.

updatesuspenseimplementation

Keep your two children nextChildren in suspense for now, button and clock in this example, but primaryChild is returned.

Create an offsetFiber whose children are two children, mode:”visible” to show it based on whether the mode is visible, Finally, we return offsetFiber, that is, there is a layer of offsetFiber sub-components between Suspense and Children, through which we judge whether the two sub-components are visible.

React will catch(error) the workloop function will catch(error) the clock subcomponent will catch(error) the clock subcomponent will catch(error) the clock subcomponent will catch(error) the workloop function react will catch(error) the clock subcomponent will catch(error) the clock subcomponent will catch(error) the clock subcomponent will catch(error) the clock subcomponent will catch(error) the clock subcomponent will catch(error) the workloop function react In suspense, keep raising the error promise up to find the latest suspense component and add the promise to the update Ue of Suspense fiber. The Promise’s resolve method is then executed, triggering root’s update that causes the view to be rerendered. (Not sure)