As a library for building user interfaces, React’s core has always been around the important goal of updating. Combining updates with the ultimate user experience is something the React team has been working hard on. How does React make the user experience so good? I think this is based on the following two reasons:

  • Fiber architecture and Scheduler’s excellent scheduling mode enable asynchronous and interruptible update behavior.
  • The priority mechanism runs through the update cycle

This article is the first in a series of articles on the React Principle. Before we start, we will introduce the React principle based on these two points so that we can have a basic understanding of some concepts.

React will be updated with the main React version. Feel free to download and debug it.

What is the Fiber

What is Fiber? It’s the smallest unit of work for React, where everything can be a component. On a normal HTML page, multiple DOM elements can be artificially grouped together to form a component. An HTML tag can be a component (HostComponent), and a plain text node can be a component (HostText). Each component corresponds to a Fiber node. Many fiber nodes are nested and associated with each other to form a Fiber tree, just as the relationship between the Fiber tree and DOM is shown below:

Fiber div # the DOM tree root div # root | | < App / > div | \ div/p a / ↖ ↖ p -- -- -- -- > < Child / > | aCopy the code

A DOM node must correspond to a Fiber node, but a Fiber node does not necessarily correspond to a DOM node.

As a unit of work, fiber has the following structure:

function FiberNode( tag: WorkTag, pendingProps: mixed, key: null | string, mode: TypeOfMode,) {// The static attribute of the Fiber element is related to this.tag = tag; this.key = key; // Fiber key this.elementType = null; this.type = null; // fiber corresponds to the DOM element tag type, div, p... this.stateNode = null; // This. Return = null; // This. Fiber this.child = null; Fiber this.sibling = null; Fiber this.index = 0; this.ref = null; // ref related // Fiber related this. PendingProps = pendingProps; this.memoizedProps = null; this.updateQueue = null; // memoizedState = null; // Set the following parameters to be used: // Set the following parameters to be used: this.mode = mode; // Effects // this. Flags = NoFlags; // Effects // this. this.nextEffect = null; FirstEffect = null; firstEffect = null; this.lastEffect = null; this.lanes = NoLanes; This. ChildLanes = NoLanes; // The priority in the subtree, which can determine whether the subtree of the current node needs to be updated /* * can be considered as the same node in the workInProgress (or current) tree, * */ this. Alternate = null; * */ this. Alternate = null; }Copy the code

How is React updated in Fiber

React updates are performed in two phases: Render and Commit. The two phases can be summarized as the construction of the new Fiber tree and the application of the update effect.

Render phase

The Render phase actually builds a new fiber tree (called the workInProgress tree) in memory by traversing it depth-first from root and back to root, Each fiber node goes through two phases: beginWork and completeWork. The state calculation of the component, the diff operation, and the execution of the render function occur in the beginWork phase, and the collection of the effect list, and the collection of skipped priorities, occur in the completeWork phase. Building the workInProgress tree has a pointer to the workInProgress that records which Fiber node is currently being built to, which is one of the important reasons why the React update task can be recovered.

The following GIF is a brief render phase:

The commit phase

After the Render phase is over, the commit phase, which cannot be interrupted, is used to complete DOM operations and apply updates to the page based on the changed nodes in the workInProgress tree (the effect list collected by the completeWork process in the Render phase). In addition, useEffect is scheduled asynchronously and useLayoutEffect is executed synchronously.

These two phases are independent React tasks, which are finally scheduled by the Scheduler. The scheduling priority adopted in render stage is determined according to the priority of this update, so that the intervention of high-priority tasks can interrupt the work of low-priority tasks; The scheduling priority of the COMMIT phase is set to the highest to ensure that synchronization cannot be interrupted.

The role of the Scheduler

Scheduler is used to schedule the execution of the React tasks mentioned above.

What is scheduling? Determines which task will be executed first based on task priority. The goal of scheduling is to ensure that high-priority tasks are executed first.

What is enforcement? Scheduler has a feature to execute tasks: it terminates tasks according to time slices and judges whether the tasks are completed. If not, it continues to call task functions. It only interrupts and resumes tasks and relies on React to tell it if a task has completed. Scheduler and React work together to make React tasks asynchronous and interruptible.

Priority mechanism

To prioritize tasks, React has an event to schedule priority mechanism. Events themselves take priority attribute, it will lead to update based on event priority compute update their priority, update will produce the update task, the update task priority by update priority calculation, the update task scheduling, so need to be coordinated scheduling process scheduling priority, calculated by the update task priority scheduling priority, so step by step, React uses the concept of priority throughout the update lifecycle.

React Priority For more information about React priorities, see Priority in React.

Double buffering mechanism

The dual buffering mechanism is a way to manage updates in React and an important mechanism to improve user experience.

When React starts updating, there will be two Fiber trees and one current tree, which corresponds to what is currently displayed on the page. The other is the workInProgress tree, which is a new Fiber tree based on the depth-first traversal of the Current tree. All updates will eventually be reflected in the workInProgress tree. When the update is completed (at the end of the COMMIT phase), the fiber tree will switch from the current tree to the workInProgress tree. The workInProgress tree becomes the new Current tree.

function commitRootImpl(root, renderPriorityLevel) {...// finishedWork is the root of the workInProgress tree,
    // root.current points to it to complete the tree switchroot.current = finishedWork; . }Copy the code

The relationship between the two trees at the commit stage is shown in the following figure. At the end of the commit stage, the two trees will switch.

The old content is still displayed before the update is completed, and the interaction is maintained. When the update is completed, the new content can be switched to the new content immediately, so that the new content and the old content can be seamlessly switched.

conclusion

This article Outlines the workflow and roles of React. This series of articles will focus on the update process, from the Render phase to the Commit phase, and explain how React works. In addition, there is a lengthy analysis of other highlights, such as the event mechanism, the Scheduler principle, the Hooks principle and the Context principle.

This series of articles takes a long time. When I write, version 17 has not been released, so I refer to the source code versions 16.13.1, 17.0.0-alpha.0 and 17.