Hello, everyone. I am Karsong.

React has a fiber-based scheduling system.

The basic functions of this scheduling system include:

  • Updates have different priorities

  • A single update may involve render of multiple components, which may be assigned to execute in multiple macro tasks (i.e., time slices)

  • High-priority updates interrupt low-priority updates in progress

This article uses 100 lines of code to quickly understand React scheduling.

I know you don’t like looking at large chunks of code, so this article will explain how it works in the form of diagrams and snippets.

There’s a full online Demo at the end of this article so you can try it out for yourself.

On the whole!

Welcome to join the Human high quality front-end framework research group, Band fly

The preparatory work

We use the work data structure to represent a job, and work.count represents the number of times a job has to do something repeatedly.

The thing to do repeatedly in the Demo is “execute insertItem to insert into the page” :

const insertItem = (content: string) = > {
  const ele = document.createElement('span');
  ele.innerText = `${content}`;
  contentBox.appendChild(ele);
};
Copy the code

Therefore, for the following work:

const work1 = {
  count: 100
}
Copy the code

InsertItem 100 times inserts 100 to the page.

Work can be analogous to an update to React, and work.count is analogous to the number of components to render. So Demo is an analogy to the React update process

To achieve the first version of the scheduling system, the process is shown in the figure:

There are three steps:

  1. Insert the work to the workList queue, which holds all the work

  2. The Schedule method fetches work from the workList and passes it to Perform

  3. Repeat Step 2 after the Perform method performs all the work of work

The code is as follows:

// Save all work queues
const workList: work[] = [];

/ / scheduling
function schedule() {
  // Fetch a work from the end of the queue
  const curWork = workList.pop();
  
  if(curWork) { perform(curWork); }}/ / execution
function perform(work: Work) {
  while (work.count) {
    work.count--;
    insertItem();
  }
  schedule();
}
Copy the code

Bind click-to-click interactions for buttons, and the basic scheduling system is complete:

button.onclick = () = > {
  workList.unshift({
    count: 100
  })
  schedule();
}
Copy the code

Click button to insert 100 .

The React analogy is: click a button, trigger a synchronous update, and 100 component render

Let’s make it asynchronous.

Scheduler

React uses Scheduler internally to perform asynchronous scheduling.

Scheduler is a standalone package. So we can use him to modify our Demo.

Scheduler presets five priorities, descending from top to bottom:

  • ImmediatePriority, the highest synchronization priority
  • UserBlockingPriority
  • NormalPriority
  • LowPriority
  • IdlePriority, the lowest priority

The scheduleCallback method receives the priority and callback function fn, which is used to schedule FN:

// Schedule the callback function fn with LowPriority priority
scheduleCallback(LowPriority, fn)
Copy the code

Within Scheduler, the data structure Task is generated after executing the scheduleCallback:

const task1 = {
  expiration: startTime + timeout,
  callback: fn
}
Copy the code

Expiration indicates the expiration time of task1. Scheduler will execute expired task.callback first.

StartTime in expiration is the current startTime, and timeout is different with different priorities.

For example, the timeout of ImmediatePriority was -1, due to:

startTime - 1 < startTime
Copy the code

So the ImmediatePriority will expire immediately and the callback will execute immediately.

The corresponding timeout of IdlePriority is 1073741823 (the largest 31-bit signed integer), and the callback takes a long time to execute.

Callback is executed in the new macro task, which is how Scheduler scheduling works.

Use Scheduler to modify the Demo

The process after transformation is shown as follows:

Before transformation, work is directly fetched from the end of the workList queue:

/ / before modification
const curWork = workList.pop();
Copy the code

After modification, work can have different priorities, represented by the Priority field.

For example, work stands for NormalPriority to insert 100 :

const work1 = {
  count: 100.priority: NormalPriority
}
Copy the code

Therefore, work of the highest priority is used every time after transformation:

/ / after transforming
// Sort the workList with the minimum priority (the smaller the value, the higher the priority)
const curWork = workList.sort((w1, w2) = > {
   returnw1.priority - w2.priority; }) [0];
Copy the code

Change of process after transformation

Perform bind(NULL, work) is executed by scheduleCallback.

That is, if certain conditions are met, a new task is generated:

const someTask = {
  callback: perform.bind(null, work),
  expiration: xxx
}
Copy the code

Also, work’s work is interruptible. Perform perform performs all of the work before performing:

while (work.count) {
  work.count--;
  insertItem();
}
Copy the code

After transformation, the execution process of Work may be interrupted at any time:

while(! needYield() && work.count) { work.count--; insertItem(); }Copy the code

See the online Demo at the end of this article for an implementation of the needYield method (and when it breaks)

High priority interrupts low priority examples

Here’s an example of a high priority interrupting a low priority:

  1. Insert a low prioritywork, attributes are as follows
const work1 = {
  count: 100.priority: LowPriority
}
Copy the code
  1. experienceschedule(dispatch),perform(execute), suddenly inserts a high priority after 80 jobs have been executedworkAt this time:
const work1 = {
  // Work1 has been executed for 80 times, with 20 more to complete
  count: 20.priority: LowPriority
}
// The newly inserted high-priority work
const work2 = {
  count: 100.priority: ImmediatePriority
}
Copy the code
  1. Work1 Work interruption, continue schedule. If work2 has a higher priority, perform 100 times

  2. After the execution of work2, continue schedule and perform the remaining 20 times of work1

In this example, we need to distinguish between two concepts of interruption:

  1. In Step 3, the work performed by work1 is interrupted. This is a microcosmic interruption

  2. Since work1 was interrupted, schedule continues. The next work to be executed is the higher-quality work2. The arrival of Work2 causes work1 to be interrupted, which is the interruption from the macro perspective

The distinction between macro and micro is made because micro interrupts do not necessarily mean macro interrupts.

For example, work1 was interrupted due to exhaustion of time slices. If there is no other work with higher quality to compete with his schedule, the next perform is still Work1.

In this case, there are many interruptions at micro level, but the same work is still executing at macro level. That’s how time slices work.

The implementation principle of scheduling system

The following is the complete implementation principle of the scheduling system:

Compare with the flow chart:

conclusion

This article is a simple implementation of the React scheduling system, which consists of two phases:

  • schedule

  • perform

Here is the full Demo address.