preface
React 18 is currently in a fast iteration phase, and a stable version will be available soon, allowing us to use the new feature Concurrent provided by the stable version. Concurrent mode transforms react’s synchronous blocking rendering into interruptible asynchronous rendering, greatly improving the user experience.
In order to gain a deeper understanding of Concurrent features and first-hand knowledge, the idea of reading the React source code came into being. After several months of study, I finally had a clear understanding of the principle of React. Here, I’m going to post some of what I’ve learned. On the one hand, IT’s a summary of what I’ve learned. On the other hand, I want to share what I’ve learned with you to help you use React and read the source code.
React source code is already available online. Here, I would like to recommend a very good learning material to you – The React Technology revealed by Big Brother Kasong. In the process of reading the source code, he repeated reading several times, benefited a lot, here to pay tribute to Kasong big brother 👍🏻.
Well, without further ado, let’s cut to the chase. (This article contains a lot of images, please wait for loading while browsing, 😂)
Matting knowledge
The React process involves a lot of points, It includes fiber Tree architecture, task Scheduler Scheduler, Reconciler, Renderer, component update and priority determination, Concurrent mode and Legancy mode, etc. If on the direct explanation of these things, it is estimated that the people will be a face meng force, do not want to see, combing the people also do not know how to start 😂.
In order to help you understand the react process in a friendly way, we need to lay the groundwork.
1. Why are frameworks like React and Vue so popular
Front-end development, in the end, is all about dealing with the DOM Tree. In the days of jquery and beyond, developers had to do the DOM Tree themselves, which was laborious and time-consuming, and did not guarantee optimal performance.
Frameworks like React, Vue, and others (which haven’t used Angular 😓) have been a boon. They free us from the complexities of DOM manipulation and focus on the concrete business implementation, greatly increasing productivity. At the heart of frameworks like React and Vue are data-driven views. When using the framework, we only need to focus on data changes, not dom manipulation, and the framework itself automatically helps us to update the DOM Tree in an optimal way.
2. react element
When writing the React component, everyone uses the JSX syntax as follows:
const Component = () => {
const list = [1, 2, 3];
return <ul>{list.map(item => <li key={item}>{item}</li>)}</ul>
}
Copy the code
The JSX template is translated via Babel into a react. createElement function call when the React project is packaged and built.
const Component = () => {
const list = [1, 2, 3];
return React.createElement("ul", null, list.map(item => React.createElement("li", { key: item}, item)));
}
Copy the code
When the Component function is called, the React. CreateElement method is executed and returns an AST object called the React Element.
{
$$typeof: Symbol(react.element),
key: null,
ref: null,
type: 'ul',
props: {
children: [{
$$typeof: Symbol(react.element),
key: 1,
ref: null,
type: 'li',
props: { children: 1},
...
}, {
$$typeof: Symbol(react.element),
key: 2,
ref: null,
type: 'li',
props: { children: 1},
...
}, {
$$typeof: Symbol(react.element),
key: 3,
ref: null,
type: 'li',
props: { children: 1 },
...
}]
},
...
}
Copy the code
AST (Abstract Syntax Tree) is an Abstract representation of source code Syntax structure. In React, the React Element, as an AST, is a tree data structure abstracted from the JSX template. It plays a very important role in react (details will be explained in the following chapters).
3. From JSX to DOM structure
Let’s start with a simple React example: template
In the example above, react automatically converts our template into a DOM structure when the application starts.
From template to DOM, the main process is as follows:
Fiber Tree is the core of the whole process. There is a mapping between Fiber Tree and DOM Tree, and the related nodes of the two trees will correspond one by one. When the Fiber Tree structure changes, the DOM Tree is updated accordingly.
React Element to Fiber Tree
Let’s use a simple example to show how a React Element can be converted to a Fiber tree.
First, a react element with a relatively simple structure:
{
type: 'A'
props: {
children: [{
type: 'B',
props: {
children: [{ A
type: 'e', |
props: {} B - C - D
}] | |
} E F - G
}, {
type: 'C',
props: {
children: [{
type: 'F',
props: {}
}, {
type: 'G',
props: {}
}]
}
}, {
type: 'D',
props: {}
}]
}
}
Copy the code
Create fiber Tree with React Element
Combined with the above diagram, we can make a conclusion:
-
There are three types of Pointers in fiber Tree: Child, Sibling, and Return. Where, child points to the first child, sibling points to the sibling, and return pointer points to the parent;
-
Fiber Tree adopts depth-first traversal. If a node has child nodes, the child nodes are traversed first. After traversal of the child node is complete, the sibling node is traversed again. If there is no child node or sibling node, the parent node is returned and the sibling nodes of the parent node are traversed.
-
When the node’s return pointer returns null, the fiber Tree traversal ends.
React Update nature
Those of you who have used React know that when we call setState to change the state, the React update is triggered. When react updates are complete, the dom Tree structure of the page will change, and the methods defined in the component such as componentDidMount, componentDidUpdate, useEffect callback will also trigger.
The React update changes the structure of the Fiber Tree. The change of fiber Tree structure caused a series of side-effects, such as dom Tree structure change, component lifecycle method execution, etc. React will deal with side effects after fiber Tree updates.
There are different operations to update the Fiber tree, for example, unmount the discarded Fiber node, mount the newly created fiber node, and update-update the fiber node. Different operations cause different side effects.
Here we use an example: Fiber Tree structure update to show you the various operations of fiber Tree update and the corresponding side effects.
The following is an example:
In the example above, we trigger the change of the Fiber Tree structure by clicking the show, Hide, and Modify list buttons:
- When visible is false, the Fiber Tree unmounts the button node, Component, and child nodes;
- When visible is true, the Fiber Tree remounts the button node, Component, and child nodes;
- When we click the Modify button, the elements in the List are randomly shifted, and the children of the corresponding Component move and update their background colors.
In addition, we override the native createElement, insertBefore, and appendChild methods to listen for create and move operations on dom nodes.
Let’s take a look at the mount, unmount, and update operations mentioned above and the side effects they cause.
-
unmount
Click the Hide button. If the visible property is false, the button node, Component, and child nodes do not need to be displayed and the corresponding Fiber node is removed from the Fiber Tree. Due to the mapping between Fiber Tree and DOM Tree, the structure change of Fiber Tree causes a side effect: the structure change of DOM Tree.
In addition, unmounting a Fiber node can cause other side effects:
- Component componentWillUnmount, useEffect destory method trigger;
- Deletion of ref references (the deleted node is associated with ref);
- .
-
mount
Click the Show button, the visible property is true, the button node, Component, and child nodes need to be displayed, and the corresponding Fiber node needs to be re-created and added to the Fiber Tree. The addition of fiber node leads to the addition of dom Tree node.
Since we are listening for the createElement method in the program, when the fiber node is mounted, we can see the printed log “Create DOM node” in the console, which indicates that the new DOM node has actually been created.
There are other side effects to mounting a Fiber node:
- Component componentDidMount, useEffect callback method trigger;
- Ref application initialization;
- .
-
update
Click the Change button, the list data changes, and the list structure in the Component changes accordingly.
When we look at the log printed by the console, we find “insertBefore inserting node “and “appendChild inserting node” logs, but no “create DOM node” logs, indicating that the DOM node was not created. Only the insertBefore and appendChild methods are used to move.
Since we have modified the BACKground-color property of the DOM node, the DOM node also needs to update its property.
In addition, the Fiber node update can cause other side effects:
-
ComponentDidUpdate, useEffect callback method trigger;
-
Update of ref references;
-
Additionally, react internally divides an update into two phases: the Render phase and the COMMIT phase. The Render phase is to update the Fiber Tree and collect the side effects generated during the update. The COMMIT phase mainly deals with side effects collected in the Render phase.
React working process
With this knowledge in mind, it’s easy to understand how react works.
React works in two stages: application startup and application interaction.
The app startup phase is the entire process of rendering the first screen after we enter the app URL in the browser. In this process, the reactdom. render method is executed to build a Fiber tree from scratch and render the corresponding DOM tree according to the Fiber Tree.
After rendering the first screen, it’s time to enter the application interaction phase. As the user interacts with the page, the structure of the Fiber Tree changes, triggering updates to the page.
The creation and update of the Fiber Tree is at the heart of the React process, so to speak.
In order to better understand the whole construction and update process of fiber Tree, this section introduces the working process of React from three aspects: dual-cache Fiber Tree, creation of Fiber Tree, and update of Fiber Tee.
Fiber Tree dual cache
There are two Fiber trees in the React update process. One is the existing Old Fiber Tree, which corresponds to the content displayed on the current screen and is called current Fiber Tree. The other one is the new Fiber Tree built during the update process, called workInProgress Fiber Tree.
When the update is completed, replace the current Fiber Tree with workInProgress Fiber Tree as the next update of the Current Fiber Tree.
Fiber Tree creation
The whole creation process of fiber Tree is sorted out by using the previously used: Fiber Tree structure update. The whole process is as follows:
-
Create a React Element from the first argument passed in to the reactdom. render method;
ReactDOM.render(<App />, document.getElementById('app')); Copy the code
The above code, after being processed by Babel during compilation and packaging, is in the following format:
ReactDOM.render(React.createElement(App, null), document.getElementById('app')); Copy the code
When the render method executes, it executes React. CreateElement and returns a React element like this:
{$$typeof: Symbol(react. Element), key: null, props: {}, ref: null, type: App, // App(function component or class component) }Copy the code
After executing the react. createElement method, execute the Render method to start the fiber Tree construction phase.
-
Create a Fiber root node as the root node of the Fiber tree.
Fiber tree data structure, there must be a root node – root, so you need to create a Fiber root node for the Fiber tree.
-
Create a fiber node based on the container node as the root node of the Current Fiber Tree.
The current Fiber Tree corresponds to the current page structure. The current page has only one container node with ID =”app”, so the current Fiber Tree has only one fiber node.
-
Create a fiber node based on the container node id=”app” as the root node of the workInProgress Fiber Tree.
After the workInProgress Fiber Tree is determined, the updates and changes of fiber nodes will all take place on the wokrInProgress Fiber Tree.
-
Create a Fiber node of component type based on the React Element of the component App in step 1.
-
Execute the component App function and return a React Element.
Structure of component App:
function App() { const [visible, setVisible] = useState(true); const [list, setList] = useState(["A", "B", "C"]); Return (<div> <button onClick={() => setVisible(true)}> </button> <button onClick={() => SetVisible (false)}> hide </button> <button onClick={() => setList(list.sort() => 0.5 - Math. The random ()). Slice (0))} > change < / button > {visible && < button > button < / button >} {visible && < Component list = {list} / >} < / div >)}Copy the code
During the compilation and packaging phase, the code format after Babel processing is as follows:
function App() { const [visible, setVisible] = useState(true); const [list, setList] = useState(["A", "B", "C"]); return React.createElement("div", null, React.createElement("button", onClick: () => setVisible(true) }, "\u663E\u793A"), React.createElement("button", { onClick: () => setVisible(false)}, "\u9690\u85CF"), React.createElement("button", { onClick: () = > setList (list. Sort (() = 0.5 math.h > random ()). The slice (0))}, "\ u4FEE \ u6539"), visible && React.createElement("button", null, "\u6309\u94AE"),visible && React.createElement(Component, { list: list })); }Copy the code
When the App component function executes, the react Element returns the following:
{ $$typeof: Symbol(react.element), key: null, ref: null, type: 'div', props: { children: { $$typeof: Symbol(react.element), key: null, ref: null, type: 'div', props: { children: [{ $$typeof: Symbol(react.element), key: Null, ref: null, type: 'button' props: {children: 'show' onClick: () = > {...}},...}, {$$typeof: Symbol(react. Element), key: null, ref: null, type: 'button', props: {children: 'hide ', onClick: () => { ... } }, ... }, { $$typeof: Symbol(react.element), key: null, ref: null, type: 'button', props: { children: 'modify' onClick: () = > {...}},...}, {$$typeof: Symbol (the react. Element), the key: null, ref: null, type: 'button' props: }, {$$typeof: Symbol(react.element), key: null, ref: null, type: Component, // Component is a Component props: {list: list},... }},... }Copy the code
React Element is a tree, and the parent node can access its children through props. Children.
-
Add the React Element to the Fiber Tree.
The workInProgress pointer points to the Component node, which is a Component.
-
Execute the Component function to return a React Element.
The structure of the Component is as follows:
const Component = (props) => { useEffect(() => { console.log("mounted"); return () => { console.log("unmounted"); }; } []); Move <li key={item} style={{backgroundColor: color[index] }}> {item} </li> ))} </ul> ) };Copy the code
The code processed by Babel looks like this:
const Component = props => { useEffect(() => { console.log("mounted"); return () => { console.log("unmounted"); }; } []); return React.createElement("ul", null, props.list.map((item, index) => React.createElement("li", { key: item, style: { backgroundColor: color[index] }}, item))); }Copy the code
After executing the Component function method, the react Element returns:
{ $$typeof: Symbol(react.element), key: null, ref: null, type: 'ul', props: { children: [{ $$typeof: Symbol(react.element), key: 'A', ref: null, type: 'li', props: { children: 'A'} }, { $$typeof: Symbol(react.element), key: 'B', ref: null, type: 'div', props: { children: 'B'} }, { $$typeof: Symbol(react.element), key: null, ref: null, type: 'li', props: { children: 'C'} }] } } Copy the code
-
Convert react Element to a Sub Fiber Tree and attach it to the Fiber Tree.
-
Change the current pointer of root to the workInProgress Fiber Tree
-
Fiber Tree creation is complete, handle the corresponding side effects.
During the first screen rendering phase, fiber Tree was created from scratch. All fiber nodes were mounted for the first time, resulting in side effects including:
- The addition of all DOM nodes
- ComponentDidMount, useEffect callback function trigger;
- Initialization of ref references;
During the COMMIT phase, all side effects are handled, new DOM nodes are added to the page, and callback functions for componentDidMount and useEffect fire.
With fiber Tree built, all side effects are handled and the first screen rendering is complete.
Fiber Tree update
Again using the example: Fiber Tree structure update, let’s comb through the fiber Tree update process.
Fiber Tree structure update, involving unmount, re-mount, update operations, we through a series of diagrams to explain in detail.
-
Unmount to update
Click the Hide button to trigger the React update via setVisible(False). In this update, we need to unmount the Fiber Tree as follows:
-
In the current Fiber Tree, mark the updated fiber node.
-
Update fiber Tree
-
The Component node App needs to be updated. Execute the component function method, return a new React Element, and convert the new React Element to a Fiber node.
Because the Visible property is false, the React Element returned is changed, with no buttons, Component nodes.
The react Element structure is as follows:
{ $$typeof: Symbol(react.element), key: null, ref: null, type: 'div', props: { children: { $$typeof: Symbol(react.element), key: null, ref: null, type: 'div', props: { children: [{ $$typeof: Symbol(react.element), key: Null, ref: null, type: 'button' props: {children: 'show' onClick: () = > {...}},...}, {$$typeof: Symbol(react. Element), key: null, ref: null, type: 'button', props: {children: 'hide ', onClick: () => { ... } }, ... }, { $$typeof: Symbol(react.element), key: null, ref: null, type: 'button', props: { children: OnClick: () => {...},...}]},... }},... }Copy the code
Fiber Tree update process is as follows:
-
Set fiber root node’s current pointer to the workInProgress Tree.
-
Fiber Tree update complete, handling side effects
This update unmounts the Fiber Tree, causing some side effects:
- Dom node removal;
- Component componentWillUnmount, useEffect destory method trigger;
In addition, in practice, the unmount operation can also trigger the deletion of ref references and other side effects.
-
-
The mount update
Click the Show button to trigger the React update via setVisible(True). In this update, fiber Tree is mounted in the following process:
-
In the current Fiber Tree, mark the updated fiber node.
-
Update fiber Tree
-
The Component node App needs to be updated. Execute the component function method, return a new React Element, and convert the new React Element to a Fiber node.
-
Set fiber root node’s current pointer to the workInProgress Tree.
-
Fiber Tree update complete, handling side effects
-
-
The update to update
Click the Modify button to trigger the update of the Fiber Tree through setList(List). In this update, fiber Tree is updated as follows:
-
In the current Fiber Tree, mark the updated fiber node.
-
Update fiber Tree
-
The Component node App needs to be updated. Execute the component function method, return a new React Element, and convert the new React Element to a Fiber node.
-
Set the fiber root node current pointer to the workInProgress Fiber Tree.
-
Fiber Tree update completed, deal with side effects;
-
Write in the last
This concludes our review of how react works. If you can see here, thank you very much. Thank you for your support of this article!
Finally, let’s summarize the important knowledge points of this article:
-
From JSX to the final DOM node, go through:
- JSX templates are compiled with Babel for createElement syntax;
- Execute the component method that triggers the execution of createElement and returns react Element.
- React Element generates fiber tree;
- Generate DOM tree according to Fiber tree;
-
Update the Fiber Tree by changing the state of the react component. Update the DOM Tree after the Fiber Tree update is complete.
-
Two Fiber trees exist during react update: Current Fiber Tree and workInProgress Fiber Tree. The current Fiber Tree corresponds to the DOM structure of the current page, and the workInProgress Fiber Tree is the new Fiber tree created during the update process. After the update, workInProgress Fiber Tree will be used as the current Fiber Tree for the next update.
As the title suggests, this article only gives a brief overview of react’s working process, without giving a detailed description of its core content. The purpose of using such a long space and a large number of diagrams is to give students a visual understanding of the work process of React, so that they can easily understand the coordination process of Fiber Tree, task scheduling, component update and other knowledge points in the future.
In the next article, we will give a more detailed description of the working process of React, including the coordination process of Fiber Tree, diff algorithm, collection and treatment of side effects, etc. We hope it will help you better understand the working process of React.
The resources
- React
- React Technology revealed