The React team released the first RC version of React17 on August 11. The biggest feature of this release is “No New features”.

So, what exactly did the React team do for over a year from V16 to V17?

From V15 to V16, the React team spent two years refactoring the Stack Reconciler into Fiber Reconciler in the source architecture. It must be more than that.

In fact, this version change does have “new features” that replace the heuristic update algorithm used internally.

But this feature is insensitive to the developer.

The rest of this article will cover the following contents:

  • Origin: Why do heuristic update algorithms exist?

  • Current situation: The heuristic update algorithm of React16 and its deficiency

  • Future: A heuristic update algorithm for Act17

Why is there a heuristic update algorithm

The running performance of the framework is the key point that the framework designer should pay attention to when designing the framework.

Vue uses template syntax to optimize identified templates at compile time.

React pure JS is too flexible to optimize at compile time.

So, React optimizations are mostly at run time.

React15 pain points

React has been working hard on runtime optimizations.

For example, React15 implements batchedUpdates.

That is, multiple setStates in the context of the same event callback function trigger only one update.

However, if a single update is time-consuming, the page will still stall (which is common in a large application with a long maintenance time).

This is because the update process for Act15 is performed synchronously, and once the update starts, it cannot be interrupted until the page is rendered.

React refactoring began and continues to this day, in order to solve the problem of page-dragging caused by synchronous updates for long periods of time and to explore more possibilities for runtime optimization.

The goal of the refactoring is to achieve Concurrent Mode.

Concurrent Mode

The purpose of Concurrent Mode is to implement a set of interruptible/recoverable update mechanisms.

It consists of two parts:

  • A coroutine architecture

  • Heuristic update algorithm based on coroutine architecture

The coroutine architecture is the Fiber Reconciler implemented in Act 16.

We can understand the Fiber Reconciler as a Generator implemented by React itself.

A detailed introduction to the Fiber Reconciler from concept to source is available here

The coroutine architecture allows updates to be interrupted when needed, giving the browser time to complete the layout and drawing of styles and reducing stuttering (dropped frames).

When the browser enters the next event loop, the coroutine architecture can restore the interruption or discard the previous update and start the update process again.

The heuristic update algorithm is the algorithm that controls the working mode of coroutine architecture.

React16’s heuristic update algorithm

What is the heuristic of the update algorithm?

A heuristic refers to scheduling updates not by explicit assignment but by priority.

The priority comes from the research results of human-computer interaction.

Such as:

The research results of human-computer interaction show that:

  • When users enter content in the input box, they want the input content to respond to the input box in real time

  • When data is requested asynchronously, it is acceptable for users to wait a while before displaying the content

Based on this, in Act16

Input box input content triggeredUpdate ` `Priority > Triggered when request data is returnedUpdate ` `priorityCopy the code

Algorithm implementation

In Act16, 17, executing this.setState within a component produces a linked list data structure update within the corresponding Fiber node of that component.

Update. expirationTimes is a timestamp like field, indicating the priority.

ExpirationTimes = expirationTimes

The closer the value is to the current time, the higher the update priority is.

If update.expirationTimes expires, the update expires and the priority changes to the highest.

Multiple Updates may exist on multiple Fiber nodes in a fiber tree.

Every time a Fiber Reconciler is scheduled for update, a expirationTimes (normally the largest) is selected from all UPDATe. expirationTimes of all Fiber nodes as the priority of this update.

And build a new Fiber tree from the root fiber node down.

If a fiber node contains an update, and

update.expirationTimes >= expirationTimes
Copy the code

The state change corresponding to the update will be reflected in the update.

Every update will have a expirationTimes priority selected, and the page will be rendered as a snapshot of the update with that priority.

For example, we have the Fiber tree shown in the image, which has not been updated yet, so there is no Fiber tree under construction.

When a low-priority UPDATE is created in C, the update is scheduled with a lower priority.

Start building the new Fiber tree (right).

At this point, we create a high-priority UPDATE in D.

This interrupts the low-priority updates in progress and restarts the generation of a fiber tree at a higher priority.

Since the previous update was interrupted, no render operations have been performed, and nothing has changed in the view (left image).

This update has a high priority. The update of C will be skipped.

After the update is complete, the new Fiber tree will be rendered into the view.

Because C is skipped, it is not represented in the view (left).

Next we trigger a high-priority update in E.

C contains a low-priority UPDATE, but over time its expirationTimes expire and become high-priority.

Therefore, two fiber nodes C and E will be changed in this update.

After the final update, the view looks like this:

See here for a detailed explanation of update priorities

Algorithm of defect

If you only consider interrupt/continue CPU operations, a model with the expirationTimes size as a priority measure works fine.

But the expirationTimes model does not satisfy Suspense for IO operations.

In this model, high priority IO tasks (Suspense) interrupt low priority CPU tasks.

Remember, every update is based on a priority for the entire tree, not just a component, even if the source of the update is actually a component.

The expirationTimes model can only distinguish if >=expirationTimes.

In order to extend the capability boundary of Concurrent Mode, a more fine-grained heuristic priority updating algorithm is required.

React17 heuristic update algorithm

Ideally, you can specify any number of priorities, and updates will generate page snapshots with those priorities corresponding to updates.

However, under the existing architecture, there are bottlenecks in the implementation of this scheme.

The compromise solution for React17 is to specify a sequential priority range, and each update will generate a snapshot of the page with the priority contained in the range.

This priority interval model is called lanes.

To do this, a 31-bit binary is used to represent 31 possibilities.

  • Each bit is called a lane and represents priority

  • A binary number of lanes is called an lanes, representing a number of priorities

As you can see from the source code, each bit from the blue line corresponds to a lane or an lanes.

When an update occurs, it is given one of the following priorities according to the same heuristic used in React16:

export const SyncLanePriority: LanePriority = 17;
export const SyncBatchedLanePriority: LanePriority = 16;
export const InputDiscreteLanePriority: LanePriority = 14;
export const InputContinuousLanePriority: LanePriority = 12;
export const DefaultLanePriority: LanePriority = 10;
export const TransitionShortLanePriority: LanePriority = 8;
export const TransitionLongLanePriority: LanePriority = 6;
Copy the code

A higher value indicates a higher priority.

Such as:

  • Click event callback is triggered. This update will get InputDiscreteLanePriority setState.

  • Synchronized updates get SyncLanePriority.

Next, Update looks for unoccupied lanes using Priority as a clue.

If the current Fiber tree already has an update and the updated lanes contain that lane, the update needs to look for other lanes.

For example, corresponding lanes for InputDiscreteLanes InputDiscreteLanePriority.

// The fourth and fifth digits are 1
const InputDiscreteLanes: Lanes = 0b0000000000000000000000000011000;
Copy the code

The lanes contain the fourth and fifth bits and two bits.

If one

// The fifth digit is 1
0b0000000000000000000000000010000
Copy the code

If the fifth lane is already occupied, the update can attempt to occupy the latter lane, i.e

// The fourth digit is 1
0b0000000000000000000000000001000
Copy the code

If InputDiscreteLanes two lane are occupied, the priority of this update will decline to the lane InputContinuousLanePriority and continue to look for free.

The process is like this: the mall has lanes on each floor (different priorities), with multiple parking Spaces (lanes).

First we drive to the top floor and look for a parking space, “lane.” If there is no parking space, we go down to the first floor and continue looking.

Until we find a free space.

Because Lanes can contain multiple lanes, it is easy to distinguish IO operations from CPU operations.

Suspense lanes are inserted into the lanes selected for this update when building the Fiber tree goes into building Suspense subtrees.

Suspense Lane will be removed from Lanes for this update when the build leaves the Suspense subtree.

conclusion

React16’s expirationTimes model can only differentiate if >=expirationTimes determines whether a node is updated.

React17’s Lanes model can select an update interval and dynamically add or subtract priorities to the interval to handle more fine-grained updates.

Finally, we recommend a new source code for React: React Technology Revealed