Welcome to subscribe to
React Technology revealedRefer to React 16.13.1 for the code.
How is it different from other React tutorials?
Let’s assume that React is the framework for your daily development. As you develop it day in and day out, you get the idea to learn the React source code. After a quick search on the Internet, you find that these tutorials fall into two categories:
-
“Xx lines of code with you to achieve mini React”, “XX lines of code to achieve React Hook” such short and short articles. If you just want to spend a little time understanding how React works, I recommend this article. It’s great.
-
React Fiber Principle, React expirationTime Principle If you want to learn the React source code, you don’t know what Fiber is and you don’t know what expirationTime means when you want to React. If you want to learn the React source code, you don’t know what Fiber is and you don’t know what expirationTime means when you want to React.
This series of articles and corresponding warehouses exist to address this problem.
In short, this series of articles will explain why React does what it does and how to do it in general, but there won’t be big chunks of code telling you how to do it.
When you’ve read the article and know what we’re going to do, look at the concrete code implementation in the repository.
At the same time, in order to prevent too much code from affecting your understanding of the implementation of a certain function, I have made a Git tag for the implementation of each function in the repository.
How to use the supporting warehouse?
RectDOM.render(<App/>.document.getElementById('app'));Copy the code
No state, no Hooks, no function components, no class components, only the first screen elements can be rendered, but all the directory architecture, file names, methods are the same as React, and the code snippets are exactly the same (because they are copied while debugging).
If you want to read the React source code but are discouraged by the sheer volume of code that React has, I believe this project is right for you to get started.
npm startCopy the code
This is the first article in this series, corresponding to Git Tag V1
schedule + render + commit = React
As we know, React is a declarative UI library. We declare the UI as a component, and React will output the DOM for us and render it to the page.
In React, UI declarations are made through a syntactic sugar called JSX. JSX is Babel converted to the React. CreateElement method at compile time.
What we get at run time is actually
An object that describes the structure of a component.
/ / input JSX
const a = <div>Hello</div>
// At compile time, it is compiled by Babel into the React. CreateElement function
const a = React.createElement('div'.null.'Hello');
// At run time, the function executes, returning an object describing the structure of the component
const a = {
? typeof: Symbol(react.element),
"type": "div"."key": null."props": {
"children": "Hello"}}Copy the code
As we can see, the object that describes the component structure at runtime is far from the DOM that is rendered to the page. In order to render the DOM to the page, React must have two modules inside:
- Is responsible for parsing JSX objects and determining which JSX objects need to be finally rendered as DOM nodes.
- Render the DOM elements that need to be rendered onto the page.
In React, we call module 1’s work render and module 2’s work commit.
To get the name, think about your ClassComponent render method. One of the things you do in the Render phase is execute the render method.
As for commit, you might think of Git commit. In fact, React’s workflow is very similar to Git multi-branch development.
So, let’s update our architecture:
Schedule Phase analysis
So far, we’ve briefly covered Render and Commit, and with these two phases, we’ve been able to implement most of the React functionality except Concurrent.
However, consider the following scenario:
- Character changes entered by the user in the input box
-
The real-time matching results are displayed in the drop-down list
In other words, if the priority of 1 is higher than that of 2, the user experience must be better.
Even at the extreme, we have triggered 2 and the user triggered 1 in the process of calculating the DOM nodes that 2 needs to change. If 2 is shelved and 1 is prioritized, the experience is expected.
So we need a mechanism to handle the priority of updates and decide which updates due to state changes should be executed first.
To do this, we knew we needed to add one to the existing architectureschedulePhase:
- schedulePhase, when the trigger state changes,scheduleThe phase determines the priority of the triggered updates and informs the Render phase which updates should be processed next.
- renderPhase, copy.schedulePhase notification, handles updates to the corresponding JSX, and determines which JSX objects need to be finally rendered.
- In the Commit phase, the content that needs to be rendered from the Render phase is rendered onto the page.
Analysis of the COMMIT phase
Based on our current design, the Commit phase is responsible for rendering the DOM elements that need to be rendered onto the page.
But React’s ambitions have never been limited to the Web, theoreticallyrenderPhase determines which JSX needs to be rendered after we correspond to the different onescommit, you can render on different platforms.
-
ReactDOM renders to the browser
-
ReactNative renders App native components
-
ReactTest renders pure Js objects for testing
-
ReactArt render to Canvas, SVG or VML (IE8)
Render’s smallest unit — Fiber
-
Due to the
renderPhases can produce results that correspond to multiple platforms
committhat
renderThe results produced by phases cannot be platform dependent. if
renderThe nodes generated in this phase are all DOM nodes, which obviously cannot be used in the Native environment
commit. So we need a platform independent node structure. -
The JSX we input is an object that describes the structure of the component, but it can’t describe which node updates and which node deletes such node behavior, so we need a structure that describes the behavior of the node.
-
They talk about the
schedulePhase, we want low priority
scheduleCan be terminated to restart at a higher priority
schedule. then
scheduleThe node granularity must be fine enough so that we can fully manipulate node terminations
scheduleAnd clear the node
schedule
The result of that starts all over again.
When we try to render
- In this example, App node is a function component node, DIV node is a native DOM node, and I am node is a text node.
- You can save information about a node (such as state, props).
- You can store values for nodes (such as App nodes for App functions, div nodes for DIV DomElements). This structure also explains why function components can save state via Hooks. State is not stored on the function, but on the Fiber node corresponding to the function component.
- You can save the behavior of a node (update/delete/insert), as described later
In React, our components form a tree of components. Once we have the Fiber structure, we need to link them together to form a tree of components. We added the following fields to Fiber:
- Child: refers to the first subfiber
- Sibling: Points to the sibling node on the right
Do you have a lot of…?
As Andrew Clark of the React Core team explains, return refers to the Fiber that is returned after the current Fiber is processed. When the child Fiber is processed, it will return to its parent Fiber. All right.
So our complete Fiber structure looks like this:
The overall flow of Render and Commit
Now that we have the node type (Fiber) describing the component, we are happy to begin the first screen rendering.
It should be noted that since the first screen render generated by performing reactdom.render does not involve other higher-priority updates, for the first screen render we will skip the Schedule phase.
For example, in the example of the address input box shown in the Schedule phase, the first screen renders the input box, and higher-priority updates are generated by typing text into the input box later.
When we first enter the Render phase, we pass JSX:
The whole Render phase needs to do two things:
-
Traverse down JSX, generating and assigning the corresponding Fiber for each JSX node’s child JSX node
FiberSide effects that need to be performed, the most common side effects are:
-
Placement Inserts the DOM node
-
Update Updates the DOM node
-
Deletion of the DOM node
Placement. (all effectTag
See here,)
PS: You may wonder why this step is “generate the corresponding Fiber for each node’s child nodes” instead of “generate the corresponding Fiber for the current node”. Remember this line of code:
2. Generate DOM nodes for each Fiber and save them in fiber.statenode
After these two things are done we enter the COMMIT phase, where we know
-
Which fibers need to perform what operations (as shown by fiber.effecttag)
-
The Fiber that performs these operations corresponds to the DOM node (as shown by fiber.statenode).
With this information, the Commit phase simply iterates through all the fibers that have Placement side effects and performs DOM inserts in turn to render the first screen.
This is the whole process of render+ Commit for the first screen. If you’re smart, you don’t feel the pressure to understand.
Go into the Render phase
We just said that the render phase does two things (two functions that are called), now let’s give them a name:
beginWork
Traverse down JSX, generating the corresponding Fiber for each JSX node’s child JSX node and setting the effectTag
We call it beginWork, which is the starting point for each node render phase to start work.
completeWork
Generate a DOM node for each Fiber
We call it completeWork, which is the end of the render phase of each node to complete the work.
We use the global variable workInProgress to represent the Fiber being processed by the current Render phase. When the first screen render is initialized, workInProgress === root Fiber.
Call the workLoopSync method, which internally loops through the performUnitOfWork method.
PerformUnitOfWork receives one Fiber each time, calls beginWork or CompleteWork, and returns the next Fiber to be processed after processing this Fiber.
When performUnitOfWork returns NULL, the render phase is over for all nodes.
Although the whole process seems cumbersome, only two things have been done:
-
Using depth-first traversal, generate subfiber from top to bottom, and continue to traverse the subfiber after generation (code)
-
When there are no subfibers in the loop, the loop starts from the bottom up, creating the DOM nodes (code) for each Fiber created in step 1.
If a sibling node is encountered during this process, step 1 is repeated until you finally return to the root Fiber and complete the tree creation and traversal.
Optimize the render phase
effectList
In our design, the Commit phase iterates to find all Fiber nodes that contain effecttags. If the Fiber tree is large, this traversal can be time-consuming.
In fact, in the Render phase we already know which fibers will be set to Fiber. EffectTag, so we can mark them in advance in the Render phase and organize them into a linked list.
Suppose that the Fiber marked red in the figure represents that the Fiber has an effectTag in this scheduling. We use the pointer of the linked list to link them together to form a unidirectional linked list, and this linked list is the effectList.
In the words of Redux author Dan Abramov, the effectList is like an Easter egg on a Christmas tree compared to the Fiber tree
With an effectList in place, the Commit phase simply traverses the list to know all the fibers that have an effectTag. This code is in the completeUnitOfWork function.
What’s special about first screen rendering
According to our architecture, we assign a value to the Fiber that needs to be inserted into the DOM
fiber.effectTag = Placement;Copy the code
This is fine for an incremental update, but it’s inefficient for first-screen rendering, where all the DOM nodes corresponding to the Fiber node need to be rendered to the page.
Do we assign the value effectTag = Placement to all fibers; Do DOM inserts again and again during the COMMIT phase to generate a whole DOM tree? For the first screen render, we need to be a little flexible.
When we completeWork to create the DOM node corresponding to the Fiber in the Render phase, we go through all the child nodes of the Fiber node and insert the DOM node of the child node under the created DOM node.
(The completeWork of the child Fiber is executed before the parent Fiber, so there must be a DOM node for the child Fiber when the parent Fiber is executed). See the code here
By the time we get to the root Fiber node, we already have an off-screen DOM tree built, and we just need to assign the root’s effectTag to mount the entire DOM tree at once during the Commit phase.
// assign only RootFiber to one node effectTag RootFiber.Copy the code
What happens before the Render phase
// assign Rootfiber workInProgress = Rootfiber;Copy the code
What happened in between?
ReactDOM. Render creates a RootFiber, which it assigns to the workInProgress
-
ReactDOM.render
-
this.setState
-
tihs.forceUpdate
-
useReducer hook
-
UseState Hook (PS: useState is actually a special useReducer)
{/ / UpdateState | ReplaceState | ForceUpdate | CaptureUpdate tag: UpdateState, / / update the state of the content: Update next: null}Copy the code
The React ClassComponent’s this.setState call generates an update. The update. Payload is the state that needs to be updated. When beginWork is performed on the Fiber corresponding to the ClassComponent, the component state changes caused by the state update will be processed. Of course, we haven’t implemented this yet in version V1.
When calling ReactDOM. Render to initialize the root Fiber, an update will be generated, and the update.payload will be corresponding to the JSX that needs to be rendered (see the code here), and the Render process mentioned in this article will be triggered in the beginWork of the root Fiber.
At the end
Space is limited, we talk about a lot of macro things, to understand the details also need a lot of debug code, our Demo step several times.
Here I would like to recommend an excellent React principle article to you