One, foreword

React source code (V 16.4.2) will be used to illustrate how React works, and to help readers understand the relationship between ReactElement and Fiber, as well as the functions of Fiber in various processes. This article will help you read the React source code more easily. The initial plan includes the following articles:

  1. For the first time to render
  2. Event mechanism
  3. Update process
  4. Scheduling mechanism

Second, core type analysis

Before we dive into the process, take a look at the core types inside the React source code to help us understand the process. In order to make it easier to understand, the following descriptions only extract the core parts, leaving out ref, context, asynchrony, scheduling, exception handling and so on.

1. ReactElement

When we write React components, we usually use JSX to describe components.

Babel converts this to the form react. createElement(type, props, children). In our example, type will be of two types: function and String. Function normally refers to the constructor of ReactComponent or function of a functional component, while string is an HTML tag.

This method returns a ReactElement, which is an ordinary Object and is not instantiated by a class.

key type desc
? typeof Symbol|Number Object type identifier, used to determine whether the current Object is a ReactElement of a certain type
type Function|String|Symbol|Number|Object If the current ReactElement is a ReactComponent, this would be its corresponding Constructor; Normal HTML tags, they’re usually strings
props Object All properties on the ReactElement, including the special property children

2. ReactRoot

It is currently placed inside reactdom.js and can be understood as the React render entry. After we call reactdom. render, the core is to create a ReactRoot, and then call the Render method of the ReactRoot instance to enter the rendering process.

key type desc
render Function Render entry method
_internalRoot FiberRoot A FiberTree root created based on the current DOMContainer

3. FiberRoot

FiberRoot is an Object that is the core root Object for subsequent initialization and updates. The core members are as follows:

key type desc
current (HostRoot)FiberNode Point to the Root of the currently completed Fiber Tree
containerInfo DOMContainer The React DOM container renders the entire React inside the DOM
finishedWork (HostRoot)FiberNode|null Point to the Fiber Tree Root that is currently ready

Current and finishedWork are both FiberNode (HostRoot). Why? I’m going to keep you in suspense and we’ll talk about it later.

4. FiberNode

After React 16, Fiber Reconciler served as the default scheduler for React, and the core data structure was a Node Tree made up of FiberNodes. Take a look at his inner circle:

key type desc
Instance related
tag Number FiberNode type, can be in the packages/Shared/ReactTypeOfWork found in js. You can see ClassComponent, HostRoot, HostComponent, and HostText in the current demo
type Function|String|Symbol|Number|Object This parameter is consistent with ReactElement
stateNode FiberRoot|DomElement|ReactComponentInstance FiberNode uses stateNode to bind other objects, such as the Dom, FiberRoot, and ReactComponent instances for FiberNode
Fiber traversal process is related
return FiberNode|null Indicates parent FiberNode
child FiberNode|null Represents the first FiberNode
sibling FiberNode|null Represents the next closest FiberNode sibling
alternate FiberNode|null The Fiber scheduling algorithm adopts the dual buffer pool algorithm, and all nodes under FiberRoot try to create their own “mirrors” during the algorithm, which will be explained later
Data related to
pendingProps Object Represents the new props
memoizedProps Object Represents the new props after all processes have been processed
memoizedState Object Represents the new state after all processes have been processed
Side effect description correlation
updateQueue UpdateQueue Update queues, which hold the status of changes that are about to occur, more on that later
effectTag Number A hexadecimal number can be interpreted as a field to identify N actions, such as Placement, Update, Deletion, and Callback. So you see a lot of &= in the source code
firstEffect FiberNode|null Reference to the first FiberNode side effect that needs to be handled under the current node in relation to the side effect operation traversal process
nextEffect FiberNode|null Represents a reference to the next side effect, FiberNode, to be handled
lastEffect FiberNode|null Represents a reference to the last side effect, FiberNode, to be handled

5. Update

During the execution of the scheduling algorithm, the action that needs to be changed is represented by an Update data. Updates in the same queue are concatenated by the next attribute, which is essentially a single linked list.

key type desc
tag Number The value ranges from 0 to 3, including UpdateState, ReplaceState, ForceUpdate, and CaptureUpdate
payload Function|Object Represents the data content corresponding to this update
callback Function Represents the updated callback function, which, if it has a value, will hang on the current Update object in the Update Ue side effect list
next Update Updates in UpdateQueue are concatenated with next to represent the next Update object

6. UpdateQueue

The CapturedUpdate section is omitted in the FiberNode node, which represents a collection of side effects (primarily callbacks) of updates to the current node

key type desc
baseState Object Indicates the base state before the update
firstUpdate Update The first Update object reference, overall, is a single linked list
lastUpdate Update The last Update object reference
firstEffect Update A reference to the first Update object that contains a side effect (Callback)
lastEffect Update The last reference to an Update object that contains a side effect (Callback)

Three, code sample

This process description, using the following source code for analysis


//index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';

ReactDOM.render(<App />, document.getElementById('root'));



//App.js
import React, { Component } from 'react';
import './App.css';

class App extends Component {
  constructor() {
    super();
    this.state = {
      msg:'init'}; }render() {
    return (
      <div className="App">
        <p className="App-intro">
          To get started, edit <code>{this.state.msg}</code> and save to reload.
        </p>
        <button onClick={() => {
          this.setState({msg: 'clicked'}); }}>hehe </button> </div> ); }}export default App;
Copy the code

4. Rendering scheduling algorithm – Preparation stage

Start with the reactdom.render method and start preparing for rendering.

1. Initialize the basic node

Create ReactRoot, FiberRoot, and (HostRoot)FiberNode to establish their relationship with DOMContainer.

2. The initialization(HostRoot)FiberNodetheUpdateQueue

By calling reactroot.render, Then enter the packages/react – the reconciler/SRC/ReactFiberReconciler. Js updateContainer – > updateContainerAtExpirationTime – > ScheduleRootUpdate a series of method calls that create an Update for this initialization and use
ReactElement as the payload. Element of the Update. Then place the Update in the FiberNode Update Ue (HostRoot).

ScheduleWork -> performSyncWork -> performWork -> performWorkOnRoot Subsequently, the algorithm was formally implemented.

5. Rendering scheduling algorithm – Execution phase

Because this is initialized, so you need to call packages/react – the reconciler/SRC/ReactFiberScheduler js renderRoot method, generating a complete FiberNode Tree finishedWork.

1. FiberNode (HostRoot) is generatedworkInProgress, i.e.,current.alternate.

Throughout the algorithm, the main thing to do is traverse FiberNode nodes. There are two roles in the algorithm, one is the current node which represents the original form of the current node, and the other is the workInProgress/alternate node which represents the recalculation based on the current node. Two object instances are independent and refer to each other before using the alternate property. Many attributes of an object are copied and then rebuilt.

Schematic diagram of first creation result:

The core idea of this is the double buffering pooling technique, because if you want to do diff, you need at least two trees to compare. In this way, you can limit the total number of trees to 2, and the nodes and node attributes are created lazily, minimizing memory usage as the algorithm progresses. See how this double buffering works in a later update flow article.

2. Work execution cycle

The schematic code is as follows:

nextUnitOfWork = createWorkInProgress( nextRoot.current, null, nextRenderExpirationTime, ); .while(nextUnitOfWork ! == null) { nextUnitOfWork = performUnitOfWork(nextUnitOfWork); }Copy the code

The newly created FiberNode is used as Next Time to work and enters the work cycle. As can be seen from the above code, is a typical recursive loop writing method. In this way, it is written as a loop. First, it is the same as the traditional recursive loop writing method to avoid the call stack stack and call stack overflow and other problems. Second, in combination with the auxiliary variables of other Scheduler codes, the traversal can be terminated and resumed at any time.

Digging deeper into the performUnitOfWork function, we can see a similar code framework:

const current = workInProgress.alternate; / /... next = beginWork(current, workInProgress, nextRenderExpirationTime); / /...if(next === null) { next = completeUnitOfWork(workInProgress); } / /...return next;
Copy the code

As you can see, there’s some processing going on with the workInProgress node, and then it goes back to next by some traversal rule, and if next isn’t empty, it goes back to the next performUnitOfWork, otherwise it goes to completeUnitOfWork.

3. beginWork

The object of each job is primarily to process workInProgress. The workinProgress. tag identifies the current FiberNode type and updates it accordingly. The following are two of the more complex FiberNode types that are dealt with in our example, and then the more important Processupat Reconceue and reconcileChildren processes in a separate case.

3.1 HostRoot – updateHostRoot

HostRoot, also known as FiberNode of HostRoot, represents a FiberNode of HostRoot type, represented by Fiberroot.tag.

As mentioned earlier, during initial initialization, FiberNode initializes its updateQueue with child nodes ready for processing after initialization. Here are two actions:

  • Process the update queue and derive the new state-ProcesSupDatequeue method
  • Create or update FiberNodechildTo get the input (also FiberNode) of the next work cycle – ChildReconciler method

The details through these two functions are more general and will be covered separately later.

3.2 ClassComponent – updateClassComponent

The ClassComponent, which we wrote ourselves when we wrote the React code, is the App in our example.

3.2.1 createReactComponentInstance stage

For nodes that have not been initialized, this method creates the ReactComponent instance and the relationship to FiberNode primarily by using the FiberNode.type ReactComponent Constructor.

(ClassComponent)FiberNode and ReactComponent

After initialization, mount the instance, calling all the periodic methods before Component Render. During this time, state may be modified by the following process:

  • Call getDerivedStateFromProps
  • Call componentWillMount — Deprecated
  • Processes the processUpdateQueue that is invoked for the Update generated as a result of the above process
3.2.2 Completion – Creating Child FiberNode

After initializing the Component instance above, get the child ReactElement by calling render on the instance, and then create all the corresponding child FiberNodes. Finally, point workinProgress. child to the first FiberNode.

3.4 Processing node update queue – processUpdateQueue method

Before explaining the process, let’s review the update Ue data structure:

As you can see from the above structure, UpdateQueue is the container that holds the entire Update one-way linked list. The baseState inside represents the original State before the Update, and iterating through the Update lists eventually yields a new baseState.

The processing of a single Update is differentiated by update. tag.

  • ReplaceState: Returns payload directly here. If payload is a function, its return value is used as the new State.
  • CaptureUpdate: Simply willworkInProgress.effectTagSet it to clearShouldCaptureFlag bit, incrementDidCaptureTag.
  • UpdateState: If payload is a normal object, consider it as the new State. If payload is a function, the return value of the function is the new State. If the new State is not empty, it is merged with the original State to return oneA new object.
  • ForceUpdate: Just SettingshasForceUpdateTrue returns the original State.

Overall, the method of things to do, is to traverse the UpdateQueue, then calculate the final new State, and then save to workInProgress. MemoizedState.

3.5 Processing of FiberNode – reconcileChildren

After the workInProgress node completes its own processing, the child ReactElement is retrieved using the props. Children or instance.render methods. A child ReactElement may be an object, an array, a string, an iterator, and is processed for different types.

  • Let’s go through the ClassComponent andArray type childTo explain the process of creating and associating FiberNode (ReconcileChildrenArray method) :

In the page initialization phase, since there are no old nodes, the process skips the logic of location index alignment and sibling element cleanup, so the process is relatively simple.

Iterate through the ReactElement array generated by the Previous Render method, generating FiberNodes one by one. FiberNode has the returnFiber attribute and sibling attribute, pointing to its parent FiberNode and its adjacent next FiberNode, respectively. This data structure is related to the subsequent traversal process.

Now, the generated FiberNode Tree structure is as follows:

The two (HostComponent) FiberNodes in the figure are the newly generated fiberNodes. < / p > and < button >… < / button >. This method finally returns the first FiberNode, creating the relationship between ClassComponent FiberNode.child and the first FiberNode.

At this point, let’s go back to the code we just looked at:

const current = workInProgress.alternate; / /... next = beginWork(current, workInProgress, nextRenderExpirationTime); / /...if(next === null) { next = completeUnitOfWork(workInProgress); } / /...return next;
Copy the code

Means that the child just returned will be treated as next to the next work loop. Repeat this to get the following FiberNode Tree:

After generating the tree, the FiberNode (HostText) in the lower left corner is returned. Re-entering the beginWork method, since FiberNode does not have a child, the completeUnitOfWork method is entered according to the code logic above.

Note: Although the final shape of the FiberNode Tree in this example looks like this, the algorithm is actually depth traversal first, and then traversal the adjacent sibling nodes after reaching the leaf node. If a sibling node has children, it continues to expand.

4. completeUnitOfWork

Entering this process indicates that the workInProgress node is a leaf node or that all of its children have been processed. Now it’s time to finish the rest of the node processing.

4.1 Creating DomElement and handling sub-DomElement bindings

In the completeWork method, different actions are distinguished according to workinprogress. tag. Here are two important actions for further analysis:

4.1.1 HostText

As mentioned earlier, FiberNode.stateNode can be used to hold DomElement Instance. In the process of initialization, stateNode is null, so through the document. The createTextNode create a Text DomElement accordingly, node contents is workInProgress. MemoizedProps. Finally, establish a connection to your FiberNode through the __reactInternalInstance$[randomKey] attribute.

4.1.2 HostComponent

In this example, after the above HostText is processed, the scheduling algorithm will find the Sibling node of the current node for processing, so it enters the processing flow of HostComponent.

Because it is currently part of the initialization process, the process is simple and just creates a DomElement according to FiberNode.tag (currently code), which creates the node through Document.createElement. Then establish a FiberNode connection with the __reactInternalInstance$[randomKey] attribute; Set up the connection to props by __reactEventHandlers$[randomKey].

After DomElement itself is created, children, if any, are appended to the current node. I’m going to skip this step for now.

Later, the DomElement properties are initialized using the setInitialProperties method, and the content, style, class, event Handler, and so on of the node are also stored there.

Now, the entire FiberNode Tree is as follows:

After several cycles of processing, the following FiberNode Tree is obtained:

After that, go back to FiberNode (HostComponent) with the red arrow and analyze the child node processing flow that was omitted earlier.

After the current DomElement is created, append the child node to the current DomElement in the appendAllChildren method. By the above process can know, can pass workInProgress. Child – > workInProgress. Child. (- > workInProgress. Child. (. (… All child nodes are found, and the stateNode of each node is the corresponding DomElement, so by traversing in this way, all domElements can be mounted to the parent DomElement.

Finally, all fiberNodes associated with DomElement are processed and the following FiberNode overview is obtained:

4.2 Attach the effect of the current node to the end of the Effect of returnFiber

As described earlier in the basic data structure, each FiberNode has firstEffect, lastEffect, and points to an Effect FiberNode chain. When the current node is processed and ready to return to the parent node, attach the current chain to the returnFiber. Finally, a FiberNode list with all the side effects of the current FiberNode Tree is mounted on (HostRoot)FiberNode. FirstEffect.

5. The execution phase is over

After all the previous processes are completed, FiberNode (HostRoot) is returned to performWorkOnRoot as finishedWork and then to the next stage.

Vi. Render scheduling algorithm – Commit phase

The so-called commit stage is the stage where some periodic functions and Dom operations are actually performed.

This is also a list traversal, and the traversal is the effect list generated in the previous phase. Before traversal, finishedWork is placed at the end of the list because (HostRoot)FiberNode.effectTag is Callback(initialization Callback) at initialization time. The structure is as follows:

After each part is committed, the traversal node is reset to FinishedWork.firsteffect.

1. Submit the operations before the node is mounted

The current process only handles the getSnapshotBeforeUpdate method that belongs to the ReactComponent.

2. Side effects of Host (insert, modify, delete)

After traversing a node, operations are determined according to the effectTag of the node, including Placement, Update, and Deletion.

Since this is the first rendering, the Placement process will be introduced, and the rest will be explained later in How React Works (3) update Process.

2.1 Insertion Process (Placement)

To insert, you must first find two elements: parent DomElement and child DomElement.

2.1.1 Find DomElement, the nearest FiberNode parent

Click FiberNode. Return to find the nearest (HostComponent)FiberNode, (HostRoot)FiberNode, and (HostPortal)FiberNode. Then through (HostComponent) FiberNode stateNode, (HostRoot) FiberNode. StateNode. ContainerInfo, (HostPortal) FiberNode. StateNode. Conta InerInfo gets the corresponding DomElement instance.

2.1.2 Find all FiberNode nearest to the current FiberNodeSwim ion DomElement accordingly

In fact, the goal is to find all adjacent (HostComponent) FiberNodes and (HostText) Fibernodes under the current FiberNode, and then retrieve the subdomElement to be inserted via the stateNode property.

All neighboring can be understood by this picture:

The fiberNode. stateNode in the red box is the child DomElement to be added to the parent DomElement.

The traversal sequence is roughly the same as the previous FiberNode Tree generation sequence:

A) Access the Child node until the node whose FiberNode.type is HostComponent or HostRoot is found, obtain the corresponding stateNode, and append it to the parent DomElement.

B) Find sibling nodes, if any, access sibling nodes, return a).

C) Access the return node if there are no sibling nodes, or return a) if return is not the root node of the current algorithm entry.

D) If return to root node, exit.

3. Change the workInProgress/alternate/finishedWork identity

It’s a short line of code, but this is important enough to mark separately:

    root.current = finishedWork;
Copy the code

This means that after the DomElement side effects are processed, it means that the buffer tree mentioned above has completed its task and is turned over as the host, becoming the current of the next modification process. Here’s the big picture:

4. Commit the life cycle call operation after loading and changing

In this process, the effect linked list is also traversed, with different processing done for each type of node.

4.1 ClassComponent

If the effectTag of the current node has an Update flag bit, the lifecycle method of the corresponding instance needs to be executed. During the initialization phase, since the current Component is being rendered for the first time, componentDidMount should be executed, otherwise componentDidUpdate should be executed.

As I mentioned earlier, update Value also has an effect linked list. This contains the callback from each previous Update, usually from the second parameter of setState, or the callback from reactdom.render. After executing the lifecycle functions above, the effect list is iterated, executing each callback once.

4.2 HostRoot

The action is the same as the second part of the ClassComponent processing.

4.3 HostComponent

This section deals with getting focus of HostComponent loaded for the first time. If the component has autoFocus props, it will get focus.

Seven, summary

Reactdom.render reactdom.render reactDom.render reactDom.render reactDom.render

  1. Create basic objects: ReactRoot, FiberRoot, and (HostRoot)FiberNode
  2. Create a mirror of HostRoot and initialize the mirror object
  3. During initialization, FiberNode Tree is created through ReactElement
  4. FiberNode, son and Sonchild,returnThe connection
  5. Brother FiberNode passedsiblingThe connection
  6. FiberNode Tree is created with depth-first and sibling nodes are created after the FiberNode Tree is created
  7. Once the leaf node is reached, start creating FiberNode instances, such as the corresponding DomElement instance, ReactComponent instance, and pass them throughFiberNode.stateNodeCreate an association.
  8. If the ReactComponent instance is currently being created, the call is calledgetDerivedStateFromProps,componentWillMountmethods
  9. Once DomElement is created, append to the newly created DomElement if there are already DomElements in the FiberNode children
  10. After the FiberNode Tree is built, the corresponding DomElement Tree is also created, and then the submission process begins
  11. During the creation of the DomElement Tree, the currentSide effectsPass it up, and in the commit phase, you’ll find the tag and load the newly created DomElement Tree into the container DomElement
  12. double-bufferingThe roles of the two FiberNode trees were changed, and the former workInProgress became regular
  13. Execute the post-load lifecycle methods of the corresponding ReactComponentcomponentDidMount
  14. Other callback calls, autoFocus processing

The next article will describe React’s event mechanism (but it is said to be ready for refactoring), so hopefully I won’t break my plough.

React version 16.5.0 after the first post was written…