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:
-
Insert the work to the workList queue, which holds all the work
-
The Schedule method fetches work from the workList and passes it to Perform
-
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 priorityUserBlockingPriority
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:
- Insert a low priority
work
, attributes are as follows
const work1 = {
count: 100.priority: LowPriority
}
Copy the code
- experience
schedule
(dispatch),perform
(execute), suddenly inserts a high priority after 80 jobs have been executedwork
At 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
-
Work1 Work interruption, continue schedule. If work2 has a higher priority, perform 100 times
-
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:
-
In Step 3, the work performed by work1 is interrupted. This is a microcosmic interruption
-
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.