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:
- For the first time to render
- Event mechanism
- Update process
- 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)FiberNode
theUpdateQueue
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
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 FiberNode
child
To 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 createReactComponent
Instance 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 will
workInProgress.effectTag
Set it to clearShouldCapture
Flag bit, incrementDidCapture
Tag. - 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 one
A new object
. - ForceUpdate: Just Settings
hasForceUpdate
True 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 and
Array type child
To 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
- Create basic objects: ReactRoot, FiberRoot, and (HostRoot)FiberNode
- Create a mirror of HostRoot and initialize the mirror object
- During initialization, FiberNode Tree is created through ReactElement
- FiberNode, son and Son
child
,return
The connection - Brother FiberNode passed
sibling
The connection - FiberNode Tree is created with depth-first and sibling nodes are created after the FiberNode Tree is created
- Once the leaf node is reached, start creating FiberNode instances, such as the corresponding DomElement instance, ReactComponent instance, and pass them through
FiberNode.stateNode
Create an association. - If the ReactComponent instance is currently being created, the call is called
getDerivedStateFromProps
,componentWillMount
methods - Once DomElement is created, append to the newly created DomElement if there are already DomElements in the FiberNode children
- After the FiberNode Tree is built, the corresponding DomElement Tree is also created, and then the submission process begins
- During the creation of the DomElement Tree, the current
Side effects
Pass it up, and in the commit phase, you’ll find the tag and load the newly created DomElement Tree into the container DomElement double-buffering
The roles of the two FiberNode trees were changed, and the former workInProgress became regular- Execute the post-load lifecycle methods of the corresponding ReactComponent
componentDidMount
- 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…