The React release for this series of articles is 17.0.3. The react release is not available in NPMJS. It is known as react18-alpha.

Scheduling, as we often call it, can be divided into two major modules, time sharding and priority scheduling

  • The asynchronous rendering of time slice is the premise of priority scheduling
  • Priority scheduling introduces priority mechanism to control interruption and replacement of tasks based on asynchronous rendering.

In this section, we will examine the asynchronous rendering principle of React from the implementation of time sharding

  1. What is time sharding
  2. Why time sharding
  3. How does time sharding work in React
  4. A minimalist implementation of time sharding

1. React evolved

What is time sharding

As mentioned above, time slice is actually a fixed and continuous interval with intervals

Fixed: Time shards are continuous with fixed working hours: they are continuous from one shard to the next. There is a time interval before entering the next shard

These explanations are abstract and can be understood in a more general way

Fixed: Fixed work 8 hours a day continuous: I have to go to work every day interval: I will rest for some time before going to work tomorrow

Why time sharding

As we know, react’s most important and time-consuming task is node traversal.

Imagine a page with 10,000 DOM nodes and how long it would take to traverse them synchronously. And if it is synchronous traversal, in the traversal process, JS thread will always occupy the main thread, resulting in the blocking of other threads of the browser, resulting in the situation of stalling.

To solve the traversal problem, let’s take a break from traversal. During the break, we can return the main thread to the renderer thread and the event thread. In this way, we can render nodes and respond to user events in a timely manner to avoid stalling.

To achieve this, we can break down the whole process into three steps

  1. Shard open
  2. Fragments are interrupted or restarted
  3. Delay the

These three steps correspond to the three characteristics of time sharding

Implement sharding open –fixed

Time sharding is independent of the React node traversal process, so you just need to pass in the entry function of node traversal in the form of callback function, so that time sharding can determine the execution timing of node traversal.

// Entry function for node traversal
function ReconcileCoordinate () {node traversal ()}function ScheduleScheduling () {create fragments (Reconcile)}Copy the code

In the first step, we need to abstract the function to be scheduled by time sharding into a task object

functionCreate a shard (Functions that need to be scheduled) {
    constNew task = {callback: functions that need to be scheduled}}Copy the code

The second step is to set the working time of sharding. In order to facilitate the follow-up, the expiration time can be directly calculated. The working time of sharding is generally 5ms, but Scheduler will adjust it according to task priority. For better understanding, 5ms is used as the default.

const taskQueue = []
functionCreate a shard (Functions that need to be scheduled) {
    constNew task = {callback: the function to be scheduled,expirationTime: performance.now() + 5000} taskqueue.push (new task) initiate asynchronous scheduling ()}Copy the code

Each shard creation is actually the beginning of a new round of scheduling, so asynchronous scheduling is initiated at the end

Why use performance. Now () instead of date.now ()

Performance.now () returns the current page dwell time, date.now () returns the current system time. The difference is that performance.now() is more accurate and more reliable than date.now ()

  1. performance.now()The returns are microseconds,Date.now()In milliseconds
  2. performance.now()A constant rate that increases slowly, and it’s not affected by system time.Date.now()The system time is changed because the system time is affectedDate.now()Will also change

Fragment interruption and restart –continuous

Shard interrupt

In Chapter 1, we optimized the React virtual DOM structure from a tree structure to a linked list structure, making it easy to use a while loop for interruptible traversal

So if you want to combine the traversal task with time sharding and realize the function of sharding interruption, you only need to add the check of the fragmentation time expiration in the while loop

functionFragment expiration check () {
    return(perfromance. Now () - sharding start time) >=5000
}
letLucky nodes that need to be traversed =null
functionBuild nodes () {
    / * * *... This is where the node building work is done */Lucky node to be traversed = lucky node to be traversed. Next}functionNode traversal () {
    whileLucky nodes that need to be traversed! =null&&! Fragment expiration check ()) {construct node ()}}function ScheduleScheduling () {create fragments (Reconcile)}Copy the code

Shard to restart

Shard restart means that the last time shard has expired and a new time shard needs to be started.

The idea is to determine whether the next round of sharding needs to be started after the end of the previous round of sharding. If necessary, a new round of asynchronous scheduling can be initiated

functionFragment expiration check () {
    return(perfromance. Now () - sharding start time) >=5000
}
functionShard event loop () {
    letTop task = taskqueue.peek ()while(Top of stack task) {if(Fragment expiration check ())break
        constTop of stack task callback = top of stack task callback()if (typeofTop of stack callback =='function') {
            // The current task is not finished, continue to workCallback = top of stack callback}else {
            // The queue is ejected after the task is completedTaskqueue.pop ()} Top task = taskqueue.peek ()}// There are missions
    if(Top of stack task)return true
    return false
}

functionShard execution () {shard start time = performance.now()varIf there's any unfinished businesstry{Are there any tasks left unfinished = Shard event loop ()}finally {
        // Fragments restart
        ifInitiate asynchronous scheduling ()}}functionInitiate asynchronous scheduling () {
    // This is actually asynchronous execution, see spacing belowShard execution ()}Copy the code

The restart condition is to determine whether there are any more tasks in the shard task queue, and then initiate the next round of time shards

Implementation of delayed execution –Has an interval

The e nature of spacing is to delay the execution of JS, so that the browser has time to breathe, to deal with the tasks of other threads, how to give the main thread control back to the browser?

You can use the asynchronous feature to initiate the next round of time sharding for delayed execution

functionInitiate asynchronous scheduling () {
    // Return the main thread briefly to the browser
    setTimeout(() = >{fragment execution ()},0)}Copy the code

Why chooseMacro taskImplementing asynchronous execution

Microtasks do not really meet the need to give back control of the main thread.

In the event cycle, a macro task is executed first, and then tasks in the microtask queue are emptied. If new tasks are still inserted into the microtask queue during the process of emptying the microtask queue, the main thread will be released only after these tasks are completed. So microtasks are not appropriate.

Time fragmentation asynchronously executes the schemeevolution

Why not?setTimeout?

If the recursion level of setTimeout is too deep, the delay will be 4ms instead of 1ms, which will cause too long delay time

Why not?requestAnimationFrame?

RequestAnimationFramed Is executed after the microtask has been executed but before the browser is rearranged and redrawn, the timing of execution is not accurate. If the JS execution time before RAF is too long, it will still cause delay

Why not?requestIdleCallback?

The requestIdleCallback is executed after the browser rearranges and redraws, which is the browser’s idle time. In fact, the timing of execution is still inaccurate, and the JS code executed by RAF may take too long

Why is itMessageChannel?

The execution timing of MessageChannel is earlier than setTimeout

In React, asynchronous execution takes precedence over setImmediate, followed by MessageChannel, and finally setTimeout, depending on the browser’s support for these features.

Simple implementation of time sharding

All of the above code will be combined to simulate the simplest implementation of time sharding (without prioritization)

Scheduler.js

const taskQueue = []
letSharding start time = -1

// ** Time sharding core **
constFragment expiration check =() = > {
    return(perfromance. Now () - sharding start time) >=5000
}
functionShard event loop () {
    letTop task = taskqueue.peek ()while(Top of stack task) {// After each task is executed, the fragment is checked to see if it is expired
        if(Fragment expiration check ())break
        constTop of stack task callback = top of stack task callback()if (typeofTop of stack callback =='function') {
            // The current task is not finished, continue to workCallback = top of stack callback}else {
            // The queue is ejected after the task is completedTaskqueue.pop ()} Top task = taskqueue.peek ()}// There are missions
    if(Top of stack task)return true
    return false
}

functionShard execution () {shard start time = performance.now()varIf there's any unfinished businesstry{Are there any tasks left unfinished = Shard event loop ()}finally {
        // ** Time sharding core: Sharding restart **
        ifInitiate asynchronous scheduling ()}}// Instantiate MessageChannel
const channel = new MessageChannel()
constPort2 = channel.port2 channel.port1. onMessage = Fragmented executionfunctionInitiate asynchronous scheduling () {
    // Send a message to channel 1. Channel 1 receives the message and performs the sharding task
    // ** Time sharding core: delay execution of **
    port2.postMessage(null)}functionCreate a shard (Functions that need to be scheduled) {
    // ** Time sharding core: Sharding on **
    constNew task = {callback: the function to be scheduled,expirationTime: performance.now() + 5000} taskqueue.push (new task) initiate asynchronous scheduling ()}export default{create a fragment, fragment expiration check}Copy the code

ReactDOM.js

import * as Scheduler from './Scheduler'
const{Create a fragment, fragment expiration check} = SchedulerletLucky nodes that need to be traversed =null
functionBuild nodes () {
    / * * *... This is where the node building work is done */Lucky node to be traversed = lucky node to be traversed. Next}functionNode traversal () {
    // ** Time sharding core: Sharding interrupt **
    whileLucky nodes that need to be traversed! =null&&! Fragment expiration check ()) {construct node ()}}function ScheduleScheduling () {create fragments (Reconcile)}functionDispatch entry () {lucky nodes that need to be traversed = React root node Schedule ()} Schedule entry ()Copy the code

Compared with the source code implementation in React, the fragmented pseudo-code is much less logical and should be easier to understand if it is centralized.

If you’re still feeling a bit opaque, you can focus on pseudocode marked with time-sharding core annotations, in conjunction with the concepts mentioned above

conclusion

After reading this article, you probably know everything you need to know about the concept of time sharding. Time sharding, one of the new features of Act16, is not as mysterious as you might think.

In general, time sharding consists of three simple modules:

  1. Shard open
  2. The fragment is interrupted or restarted
  3. Delay the

Time sharding is one of the Scheduler’s two main features, the other being priority scheduling of tasks, which will be explained in two or three chapters. In the process of reading the source code, I think the implementation of time sharding has been very amazing, I did not expect the design of priority scheduling is an unmatched impact on me.

So I hope you can like, collect, forward, this is my first liver source code series of articles, I will work harder to output dry goods content, thank you.