22
头图

Hi everyone, I'm Kasong.

Everyone knows that React has a scheduling system based on the Fiber

The basic functions of this scheduling system include:

  • Updates have different priorities
  • One update may involve render multiple components. These render may be allocated to multiple macro tasks for execution (that is, time slice)
  • high priority update will interrupt the ongoing low priority update

This article will implement this scheduling system with 100 lines of code, allowing you to quickly understand the scheduling principle of React

I know you don't like to look at large sections of code, so this article will explain the principle in the form of diagram + code snippets.

At the end of the article, there is a complete online Demo, which you can play by yourself.

Open!

Welcome to join the human high-quality front-end framework group , take flight

Ready to work

We use work represent a job, and work.count represent the number of times that this job has to be repeated.

The Demo to be repeated in 061baa4bbd84cc is "execute the insertItem method and insert <span/> into the page":

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

So, for the following work :

const work1 = {
  count: 100
}

Representative: implementation of 100 insertItem insert 100 to the page <span/> .

work can be compared React once update, work.count analogy number of components of this update to render. So Demo is an analogy to the update process of React

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

It includes three steps:

  1. Insert work into the workList queue (used to save all work
  2. schedule method from workList taken out work , passed to perform
  3. perform method after executing work repeated after all the work Step 2

code show as below:

// 保存所有work的队列
const workList: work[] = [];

// 调度
function schedule() {
  // 从队列尾取一个work
  const curWork = workList.pop();
  
  if (curWork) {
    perform(curWork);
  }
}

// 执行
function perform(work: Work) {
  while (work.count) {
    work.count--;
    insertItem();
  }
  schedule();
}

For button binding click interaction, the most basic scheduling system is completed:

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

Click button to insert 100 <span/> .

The React is: click button to trigger a synchronous update, 100 components render

Next we will transform it into asynchronous.

Scheduler

React internally uses Scheduler complete asynchronous scheduling.

Scheduler is a separate package. So we can use it to transform our Demo .

Scheduler presets 5 priority levels, and the priority levels are reduced from top to bottom:

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

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

// 将回调函数fn以LowPriority优先级调度
scheduleCallback(LowPriority, fn)

In Scheduler inside performed scheduleCallback generated after task this data structure:

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

task1.expiration representatives task1 expiration time, Scheduler will give priority to the implementation of expired task.callback .

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

For example, ImmediatePriority of timeout is -1, because:

startTime - 1 < startTime

So ImmediatePriority will expire immediately, and callback immediately.

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

callback will be executed in the new macro task, which is the principle of Scheduler

Transform Demo with Scheduler

The process after transformation is shown in the figure:

Before the transformation, work directly taken from the tail of the workList

// 改造前
const curWork = workList.pop();

After the transformation, work can have a different priority of , which is priority field.

For example, the following work represents insert 100 \<span/\> with NormalPriority priority:

const work1 = {
  count: 100,
  priority: NormalPriority
}

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

// 改造后
// 对workList排序后取priority值最小的(值越小,优先级越高)
const curWork = workList.sort((w1, w2) => {
   return w1.priority - w2.priority;
})[0];

Process changes after transformation

Seen from the flowchart, Scheduler no longer directly executed perform , but by performing scheduleCallback scheduling perform.bind(null, work) .

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

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

At the same time, work is also interruptible. Before the transformation, perform will synchronously execute all the work in work

while (work.count) {
  work.count--;
  insertItem();
}

After the transformation, work may be interrupted at any time:

while (!needYield() && work.count) {
  work.count--;
  insertItem();
}
needYield the implementation of the 061baa4bbd8ab6 method (when will it be interrupted) please refer to the online Demo at the end of the article

High priority interrupt low priority example

For example, look at an example where high priority interrupts low priority:

  1. Insert a low priority work , the attributes are as follows
const work1 = {
  count: 100,
  priority: LowPriority
}
  1. After experiencing schedule (scheduling), perform (executing), when the job was executed 80 times, a high priority work suddenly inserted. At this time:
const work1 = {
  // work1已经执行了80次工作,还差20次执行完
  count: 20,
  priority: LowPriority
}
// 新插入的高优先级work
const work2 = {
  count: 100,
  priority: ImmediatePriority
}
  1. work1 work is interrupted, continue to schedule . Because work2 higher priority, it will enter work2 corresponding to perform , and perform 100 jobs
  2. work2 executed, continue to schedule and perform the remaining 20 work1

In this example, we need to distinguish two concepts interrupting

  1. In step 3, the work performed by work1 This is a microscopic view of interrupting
  2. Because work1 was interrupted, so continue to schedule . The next performer is the more excellent work2 . work2 soon lead to work1 be interrupted, this is a macro point of interrupted

The reason why macro/micro is distinguished is because micro-interrupting does not necessarily mean that macro-interrupting .

For example: work1 was interrupted due to exhaustion of time slices. If there is no other better work to compete with him schedule , the next time perform is still work1 .

In this case, there are multiple interruptions at the micro level, but from a macro point of view, the same work is still being executed. This is the principle of time slice

The realization principle of the dispatch system

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

Look at the flow chart:

Summarize

This article is React scheduling system, which mainly includes two stages:

  • schedule
  • perform

Here is complete Demo address .


卡颂
3.1k 声望16.7k 粉丝