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:

  1. “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.

  2. 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?

If React is a ball of yarn, then its thread ends must be

RectDOM.render(<App/>.document.getElementById('app'));Copy the code
Using this thread, I sorted out what the React first screen render would do, took them out of the React code, and commented them out a lot, so that’s the V1 version of React.

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.

Each article in this series is a study note corresponding to the repository. If you want to learn with me, you can find the git tag of the corresponding version, clone it to the local, and install the dependency

npm startCopy the code
Will open the current version of the example, with the article + debug take. Create a react app using create-react-app and run the same sample code as a reference. You will find that the rendering process of our project is consistent with that of React.

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

The result of a call to the React. CreateElement method, i.e

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:

  1. Is responsible for parsing JSX objects and determining which JSX objects need to be finally rendered as DOM nodes.
  2. 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:

There is an address search box that requests the address match of the currently entered content in real time when entering a character.



There are two state changes:

  1. Character changes entered by the user in the input box
  2. The real-time matching results are displayed in the drop-down list
When these two state changes are triggered at the same time, we generally expect no lag in the input field and an acceptable delay in updating the real-time result drop down.
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:

  1. 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.
  2. renderPhase, copy.schedulePhase notification, handles updates to the corresponding JSX, and determines which JSX objects need to be finally rendered.
  3. 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

To achieve our three phases, there are three small questions:

  1. 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.
  2. 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.
  3. 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.
To solve these three problems, React proposes a structure called Fiber, as shown below:

When we try to render
, the Fiber structure on the right is generated in the Render phase. The complete structure of Fiber is here.

  • 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:

You can see how the React team went about designing the Fiber architecture in this post.

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.

Here we take the project V1 version Demo as an example:

When we first enter the Render phase, we pass JSX:

The whole Render phase needs to do two things:

  1. Traverse down JSX, generating and assigning the corresponding Fiber for each JSX node’s child JSX node

effectTagField indicates the current
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
Of course, the first screen rendering will only be involved
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:

The code that performs this line initialization first creates a root Fiber node, so when creating a Fiber from the root Fiber down, we are always creating a Fiber for the child node. That’s the first thing to do.

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

  1. Which fibers need to perform what operations (as shown by fiber.effecttag)
  2. 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:

  1. Using depth-first traversal, generate subfiber from top to bottom, and continue to traverse the subfiber after generation (code)
  2. 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

So far, we have been very close to React. We only need to optimize two more points, and it will be like act as React.

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

At this point we’re close to implementing the first screen render of React. There’s one final step left, which is to start from

To assign a value

// assign Rootfiber workInProgress = Rootfiber;Copy the code

What happened in between?

ReactDOM. Render creates a RootFiber, which it assigns to the workInProgress

To understand this question, we need to know, excluding SSR correlation, what methods can trigger React component rendering?

  1. ReactDOM.render
  2. this.setState
  3. tihs.forceUpdate
  4. useReducer hook
  5. UseState Hook (PS: useState is actually a special useReducer)
Since there are so many ways to trigger rendering, we need a uniform mechanism to indicate that a component needs to be updated. In React, this mechanism is called update, and you can see the code here. For now we can just focus on the following parameters for update

{/ / UpdateState | ReplaceState | ForceUpdate | CaptureUpdate tag: UpdateState, / / update the state of the content: Update next: null}Copy the code
It can be interpreted this way:

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

At this point we are through React’s first screen rendering process. If you’re here, give yourself a round of applause.

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

Inside Fiber: in-depth overview of the new reconciliation algorithm in React