This article is free translation and collation, if misleading, please give up reading. The original

preface

This article will take you through the two phases of React’s new architecture — Fiber and the new Reconciliation algorithm. We’ll dig into the details of React updating state and props and handling sub-components.

The body of the

React is a javascript library for building user interfaces. Its core mechanism is to detect changes in component state and then project the updated state (existing in memory) onto the user interface. In React, this core mechanism is called Reconciliation. We call the setState method, and React is responsible for detecting whether the state or props has changed, and then rendering the component back to the screen.

The React documentation provides a very good explanation of this mechanism. React Element’s role, lifecycle functions, the Render method, and the Diffing algorithm applied to child components are all explained here. The immutable React Element tree returned from the component’s Render method is popularly known as the “virtual DOM.” In the early stage of React development, this concept helped the React team explain the operation principle of React to people, but later, people found that this concept was too vague and easy to cause ambiguity. Therefore, this concept is no longer used in the React documentation. In this article, I insist on calling it the react Element Tree.

In addition to the React Element tree, React also maintains an internal Instance tree (which in turn corresponds to component instances or DOM objects). This tree is used to store the state of the entire application. React implements the internal Instance tree starting with V16.0.0. The algorithm used to manage the tree is Fiber. If you want to see the benefits of Fiber, here’s why React uses single-linked lists and how it works in Fiber.

This is the first in a series of in-depth articles designed to help you understand the React internals. In this article, I will delve into some important concepts and data structures related to algorithms. Once we understand these concepts and data structures, we can explore the entire algorithm and some of the main functions used in traversing and handling the Fiber Node Tree. In the next article in this series, I’ll explain how React applies this algorithm to perform initial rendering of the interface and handle updates to state and props. Later, we’ll explore the implementation details of scheduler, the process for Child Reconciliation, and the mechanism for building an Effect List.

I’m going to export you some really advanced knowledge, okay? Yes. I encourage you to read this series to understand the magic behind the React Concurrent feature. I’m a big believer in reverse-engineering, so I’ll give you plenty of links to jump to the source code for Replay V16.6.0.

It’s a lot to take in throughout this article. So don’t worry if you don’t understand something right away. You need to be patient and take the time to understand it, because it’s worth it. Note that you don’t need any practical knowledge of React. React This article focuses on the inner workings of React.

context

I’ll use a simple application example throughout the series. This example looks like this: We have a button. By clicking on the button, we can increase the value of a number on the interface, as shown below:

Here is the implementation code:

lass ClickCounter extends React.Component {
    constructor(props) {
        super(props);
        this.state = {count: 0};
        this.handleClick = this.handleClick.bind(this);
    }

    handleClick() {
        this.setState((state) => {
            return {count: state.count + 1};
        });
    }


    render() {
        return [
            <button key="1" onClick={this.handleClick}>Update counter</button>,
            <span key="2">{this.state.count}</span>
        ]
    }
}
Copy the code

You can play with it here. As you can see in the code, ClickCounter is a simple component. This component has two child elements: Button and SPAN. They are all returned from the Render method of the ClickCounter component. When you click a button on the interface, the internal event handler updates the state of the component. This eventually causes the SPAN element to update its text content.

React executes various activities during the reconciliation process. For example, in this example, here are the main actions React performs when rendering for the first time and after a state update:

  • updateClickCounterThe count property of the component’s state object;
  • Get (by calling the Render method) and compareClickCounterThe latest children and their props;
  • Update the textContent attribute value of the SPAN element.

In addition, React has many other activities to execute during the Reconciliation. Call life cycle functions, update refs, etc. All of these activities are called “work” in the Fiber architecture. Different types of React Elements (whose type is indicated by the “type” field of their object) generally have different types of work. For example, React needs to create its instance object for the class Component. React doesn’t need to do this for the functional Component. As you know, in React, we have various types of React elements. For example, we have class Component, Functional Component, host Component, portal, and so on. The react Element type is determined by the first argument we pass in when we call createElement. The createElement function is the function used by the component’s Render method to create the React Element.

Before we explore the main algorithms behind the various Work and Fiber architectures, let’s familiarize ourselves with the data structures used internally by React.

React Element to Fiber node

Each component in React has a corresponding UI representation. These are the things that are returned from the render function of the component. Let’s call it “View” or “Template.” Here is the “template” for our sample ClickCounter component:

<button key="1" onClick={this.onClick}>Update counter</button>
<span key="2">{this.state.count}</span>
Copy the code

react element

Once a template is compiled by the JSX Compiler, we get a large number of React elements.

To be more precise, template is compiled by JSX Compiler to get a function call wrapped into the Render method. We can only get the element if the Render method is called.

These React Elements are the real things returned by the component’s Render method. Essentially, the render method returns neither an HTML tag nor a “template”, but a function call that returns the React Element. “Template”, “HTML tag” or, more strictly, “JSX” are just appearances, and we don’t need them at all. Here is the ClickCounter component rewritten in pure JavaScript:

class ClickCounter {
    ...
    render() {
        return [
            React.createElement(
                'button',
                {
                    key: '1',
                    onClick: this.onClick
                },
                'Update counter'
            ),
            React.createElement(
                'span',
                {
                    key: '2'
                },
                this.state.count
            )
        ]
    }
}
Copy the code

The react. createElement call in the ClickCounter component’s Render method will return two such data structures:

[{$$typeof: Symbol(react.element),
        type: 'button',
        key: "1",
        props: {
            children: 'Update counter', onClick: () => { ... }}}, {$$typeof: Symbol(react.element),
        type: 'span',
        key: "2",
        props: {
            children: 0
        }
    }
]
Copy the code

You can see React adds a property to each of these JS objects that uniquely identifies them as React elements: Typeof. In addition, we have properties that describe react Elements: type, key, and props. These values come from the arguments you passed in when you called the react. createElement function. It’s interesting to see how React represents children of the text types of the SPAN and button nodes, and how the click event handler is part of the props. Of course, there are other properties on the React Element, such as the “ref” field. However, they are beyond the scope of this article.

The React Element corresponding to the ClickCounter component does not have any props or keys:

{$$typeof: Symbol(react.element),
    key: null,
    props: {},
    ref: null,
    type: ClickCounter
}
Copy the code

fiber node

In reconciliation (pronunciation: During the component render method, data returned by the React Element will be merged into the corresponding Fiber node. These Fiber nodes form a Fiber node tree corresponding to the React Element Tree. Keep in mind that each React Element has a corresponding Fiber node. Unlike react Elment, fiber nodes don’t need to be rebuilt every time the React Element is updated. Each time it is recreated). A Fiber node is a mutable data structure for storing component state and DOM objects.

As we mentioned earlier, React needs to execute different activities for different types of React Elements. In our case, for ClickCounter, the activity simply calls its lifecycle method and render method. For the SPAN host component, the activity performs DOM operations. Each React Element is converted to a fiber node of the corresponding type. Different types of work indicate different types of Fiber nodes.

You can think of a Fiber node as a normal data structure. It’s just that this data structure represents some kind of work that needs to be done. In other words, a fiber node represents a unit of work. The Fiber architecture also provides a convenient way to track, schedule, pause, and interrupt work.

React in createFiberFromTypeAndProps function using the element from the React of element data completed from the React to the fiber node transformation for the first time. In subsequent updates, React will not recreate the existing React Elements but reuse the fiber nodes. React updates fiber node properties only when necessary using data from the React Element. React also needs to adjust the hierarchy of the Fiber node in the tree according to the Key property or remove the Fiber node when the Render method does not return the React element.

You can see all the activities and related functions for handling existing Fiber nodes in the ChildReconciler function.

Before fiber was introduced, we already had something called the React Element Tree. After introducing fiber, React creates a Fiber node for each React element. Thus, we have a Fiber Node tree corresponding to the React Element Tree. In our example, the fiber node tree looks like this:

As you can see, all fiber nodes are linked to a linked list via child, Sibling, and return. If you want to know why this design works, you can read my other article, The How and Why on React’s Usage of Linked List in Fiber.

Current tree and workInProgress tree

After the first rendering of the Application, React generates an entire Fiber Node tree to store the state of the React component that was already used to render the user interface. This tree is commonly called a current tree. When React enters the update process, it builds another node tree called the workInProgress Tree. This tree holds the application states that will be flushed into the user interface.

All fiber node work on the workInProgress Tree will be completed. When React traverses the Current tree, it creates an alternate Fiber node for each fiber node in the tree. It is this type of node that makes up the workInProgress tree. React creates alternate Fiber nodes by using data from the React Element returned by the Render method. Once all update requests have been processed and all work has been executed, React produces an alternate Fiber node tree for flushing all changes to the user interface. After mapping the Alternate Fiber Node tree to the user interface, it becomes the Current Tree.

One of the core principles of React is consistency. React always does DOM updates in one go. This means that it doesn’t show users a half-updated user interface. The workInProgress Tree is used internally in React as a “draft” and is not visible to users. As a result, React can process all the components first, and then flush the changes to the user interface.

In the React source code, you’ll see many function implementations that read fiber nodes from both the Current tree and the workInProgress Tree. Here is the signature of one such function:

functionupdateHostComponent(current, workInProgress, renderExpirationTime) {... }Copy the code

Fiber nodes on the current tree and workInProgress Tree both have an alternate field value that holds a reference to their fiber nodes. (Translator’s note: both are in a circular reference state, the pseudocode is shown as follows:)

 const currentNode = {};
 const workInProgressNode = {};
 
 currentNode.alternate = workInProgressNode;
 workInProgressNode.alternate = currentNode;
 
Copy the code

Side-effects

We can think of the React Component as a function that takes state and props as input and computes a UI representation. All other activities, such as changing the DOM structure, calling component lifecycle methods, and so on, can be considered “side-effect,” or simply “effect.” The React document also mentions the definition of effect:

You have probably done operations like data fetching, subscription, or manually changing DOM structures before. Because these actions affect other components, they can’t be done during rendering. So, we call these operations “side effects” (or effects for short)

You will see that most of the updates to the state and props will result in side effects. And because applying effect is also a type of work. So fiber nodes are a great mechanism for tracking effects other than updates. Each Fiber node can have an effect. Effect is indicated by the value of the Fiber node effectTag field.

So it can be said that after a Fiber node is processed by the update process, its effect basically defines the work of the fiber node. Specifically, for host Components (DOM Elements), their work can include “add DOM elements”, “modify DOM elements”, and “remove DOM elements”. For class Components, their work can include “Update refs” that “call componentDidMount and componentDidUpdate lifecycle functions.” For other types of Fiber nodes, other works exist.

Effects list

React handles the update process very quickly. To achieve this performance goal, React applies several interesting tricks, one of which is that to speed up iteration, React builds a Linear list for fiber nodes with effects. The reason is that iterating over a Linear list is much faster than iterating over a tree. For fiber nodes without effect, there is no need to spend time iterating over them.

The linear list targets fiber nodes that need DOM manipulation or other effects associated with them. Unlike fiber nodes in the Current tree and workInProgress Tree, which are linked to each other through the Child field, fiber nodes in the Linear List are linked to each other through their nextEffect field. It is a subset of the finishedWork Tree.

Dan Abramov once made an analogy with an “effect list.” He compared the Fiber node tree to a Christmas tree. Our “Effect list” is the wires that connect the lights to the Christmas tree. To visualize it, let’s imagine the highlighted nodes in the fiber node tree below with work. For example, our update process will cause C2 to be inserted into the DOM, D2 and C1 will change their attributes, B2 will call their own lifecycle methods, and so on. The React tree’s effct list will connect these nodes to each other, so that it can skip other fiber nodes when traversal:

From the diagram above, we can see how the Fiber nodes with effect are linked together. When React iterates through these nodes, React uses the firstEffect pointer to indicate the first element of the list. Therefore, the above illustration can be reduced to the following illustration:

Root of the fiber tree

Every React application has one or more DOM elements that act as containers. In this example, a div element with an ID value of “container” is such a container.

const domContainer = document.querySelector('#container');
ReactDOM.render(React.createElement(ClickCounter), domContainer);
Copy the code

React creates fiber root for each container. You can access it via a reference stored on the DOM element:

const fiberRoot = query('#container')._reactRootContainer._internalRoot
Copy the code

The Fiber root is where React stores references to the Fiber node tree. Fiber root stores this reference via the currnet attribute value:

const hostRootFiberNode = fiberRoot.current
Copy the code

Fiber Node tree starts with a special type of Fiber node. This fiber node is called HostRoot. It is created inside React and is treated as the parent of your topmost component. Meanwhile, FiberRoot can be traced back to HostRoot’s stateNode field:

fiberRoot.current.stateNode === fiberRoot; // true
Copy the code

You can explore the entire Fiber Node tree by visiting the topmost HostRoot. Alternatively, you can access a single Fiber node by accessing the _reactInternalFiber property of a component instance:

const fiberNode = compInstance._reactInternalFiber
Copy the code

Fiber node structure

Let’s take a look at what the ClickCounter component’s Fiber node looks like in this example:

{
    stateNode: new ClickCounter,
    type: ClickCounter,
    alternate: null,
    key: null,
    updateQueue: null,
    memoizedState: {count: 0},
    pendingProps: {},
    memoizedProps: {},
    tag: 1,
    effectTag: 0,
    nextEffect: null
}
Copy the code

Fiber node and span DOM element:

{
    stateNode: new HTMLSpanElement,
    type: "span",
    alternate: null,
    key: "2",
    updateQueue: null,
    memoizedState: null,
    pendingProps: {children: 0},
    memoizedProps: {children: 0},
    tag: 5,
    effectTag: 0,
    nextEffect: null
}
Copy the code

Fiber node is a data structure with a lot of fields. I’ve explained the use of alternate, effectTag, nextEffect fields in previous chapters. Let’s take a look at why we need additional fields.

stateNode

This field holds references to component instances, DOM objects, and react Element Types. In general, we can say that this field holds the local state associated with a Fiber node.

type

This field defines the function or class associated with the current Fiber node. For class Component, the field value is the class’s constructor function. In the case of a DOM element, the field value is the name of the HTML tag corresponding to the DOM element as a string. I often use this field to understand what a React Element associated with a Fiber node looks like.

tag

This field defines the type of fiber node. In the reconciliation algorithm, it is used to determine what work a fiber node needs to do. As mentioned earlier, the content of work is determined by the type of the React Element (i.e., the Type field). CreateFiberFromTypeAndProps function is responsible for a particular type of the react element converted to the corresponding type fiber node. In this example, the ClickCounterfiber node tag value is “1”, which means that the React Element of the Fiber node is of ClassComponent type. For a SPAN node, the tag value is 5. This means that its react Element is of HostComponent type.

updateQueue

A queue of state updates, callbacks and DOM updates.

memoizedState

Fiber node state has been used to create output fiber node state. If we are currently in the process, this field in the Fiber node stores the state that has been rendered to the user interface.

memoizedProps

Fiber node props was used to create output during the last render.

pendingProps

Fiber node props that have been updated during the current rendering, waiting to be applied to child Component or DOM elements. The update to fiber node props is done using data from the React Element.

key

Uniquely identifies each item in a children list. It is used to help React figure out which item was changed, which item was added, and which item was removed. React explains its use in more detail in this General Algorithm document.

summary

You can find instructions for fiber Node’s complete data structure here. I’ve skipped a lot of fields in this article. In particular, the child, Sibling and return fields that are used to link each fiber node into a tree structure are not mentioned in this article, but I have already explained them in this article. Fields that belong to other categories, such as expirationTime, childExpirationTime, and Mode, are all Scheduler related.

General algorithm

React performs work in two phases: render and Commit.

During the Render phase, when the user calls the setState() or react.render () methods, React performs updates to the component and then calculates what needs to be updated throughout the user interface. React creates a Fiber node for each React element in the Render phase when the component is first mounted. In the next render phase, if a Fiber node’s React element still exists, the Render method will be called on each update. If the React Element still exists, the Fiber node will be reused and updated. The final goal of the Render phase is to produce a Fiber node tree marked with effect (whether it has effect and what type of effect it is). The EFFEC field of a Fiber node describes how much work the fiber node needs to do in the commit phase. During the COMMIT phase, React receives an Effect labeled Fiber Node tree as input and applies it to its corresponding instance. Specifically, this means iterating through the Effect list to perform work: DOM updates or other actions that are visible to the user as a result of the corresponding effect.

One thing to understand is that the execution of work in the Render phase can be asynchronous. React can process one or more fiber nodes, depending on the time available, and when it needs to give way to something else, React pauses and stores the work that’s already done. Execution then picks up where it left off last time. This isn’t always the case; sometimes React will just abandon the work it’s already done and start from scratch. Work can be paused because work performed during the Render phase does not have visible interface effects to the user, such as DOM updates. In contrast, subsequent commit phases are always executed synchronously. React performs the work that is visible to the user, so the React process is completed in one go.

The “call lifecycle method” is the kind of work React needs to perform. Some of the lifecycle methods are called in the Render phase, while others are called in the Commit phase. Here are the lifecycle methods called by the Render phase:

  • [UNSAFE_]componentWillMount (deprecated)
  • [UNSAFE_]componentWillReceiveProps (deprecated)
  • getDerivedStateFromProps
  • shouldComponentUpdate
  • [UNSAFE_]componentWillUpdate (deprecated)
  • render

As you can see, some legacy lifecycle methods prefixed with “UNSAFE” (Reactv16.3 introduced this change) are implemented during the Render phase. These methods are now referred to as “abandoned lifecycle methods” in the React documentation. These methods will be deprecated in a 16.x release. Its twin, the life-cycle methods not prefixed with “UNSAFE,” will be removed in 17.0. You can find an introduction to this change and an explanation of API migration in this document.

Are you curious about the reasons behind this change?

Well, here, LET me tell you something. As we said earlier, React doesn’t produce effects, such as DOM updates, because of the Render phase. React also allows components to be updated asynchronously (and possibly even in a multithreaded fashion). However, life-cycle methods prefixed by “UNSAFE” are often misunderstood and misused in practical production. Developers often place side-effect code in these lifecycle methods. React also introduced asynchronous rendering with fiber architecture. If developers continue to do this, the program will break. Although their twin methods (those not prefixed with “UNSAFE”) will eventually be removed, they may still cause problems in future versions of the Concurrent Mode feature (you can also opt out of enabling Concurrent Mode).

Here are the lifecycle methods that are executed during the COMMIT phase:

  • getSnapshotBeforeUpdate
  • componentDidMount
  • componentDidUpdate
  • componentWillUnmount

Because the phase in which these methods are executed is synchronous, they can contain side-effect code and access DOM elements.

Ok, at this point, we have enough knowledge to understand the generalized algorithm used to traverse fiber node trees and perform work.

Render phase

Reconciliation always starts with the HostRoot node at the top of the Fiber node tree. This start action occurs in the renderRoot function. However, React skips fiber nodes that have already been processed and only handles nodes with unfinished work. For example, if you call setState deep in the component tree, React will still iterate from the top node, but it will skip to all the preceding parent nodes and head straight to the child node that called setState.

Main steps of the work loop

All fiber nodes are processed in one work loop. The following is the code implementation of the work loop synchronization section:

function workLoop(isYieldy) {
  if(! isYieldy) {while (nextUnitOfWork !== null) {
      nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
    }
  } else {...}
}
Copy the code

In the code above, nextUnitOfWork holds a reference to a FIbe node in the workInProgress Tree that has unfinished work. When React traverses the Fiber node tree, it uses this variable to determine if there are other Fiber nodes that need to be processed. Once the current fiber node is processed, the nextUnitOfWork ‘variable will either point to the next fiber node or null. Once null, React exits the work loop and is ready to commit.

There are four functions for traversing the Fiber node tree, initializing, or completing work:

  • performUnitOfWork
  • beginWork
  • completeUnitOfWork
  • completeWork

How are they used? React traverses a Fiber node tree. For demonstration purposes, we use a stripped-down implementation of these functions. Each of these four functions takes a Fiber node as input. As you walk down the React tree, you can see that the current active Fiber node (orange nodes represent active Fiber nodes) is constantly changing. You can clearly see from this animation how the algorithm switches from one branch of the Fiber Node tree to another. To be specific, process all the children’s work first and then go back to the parent node:

Note that in the figure above, the vertical line represents the Sibling relationship and the horizontal and vertical line represents the children relationship. For example, B1 has no children, and B1 has a child called c1

Here’s a link to a related video. In this video, you can pause and play back to see who the current fibe node is and what the current state of the function is. Theoretically, you can think of “begin” as “stepping into” a component, and “complete” as “stepping out” from that component. You can also play this demo. In this demo, I’ve explained exactly what these functions do. The code for this demo is as follows:

const a1 = {name: 'a1', child: null, sibling: null, return: null};
const b1 = {name: 'b1', child: null, sibling: null, return: null};
const b2 = {name: 'b2', child: null, sibling: null, return: null};
const b3 = {name: 'b3', child: null, sibling: null, return: null};
const c1 = {name: 'c1', child: null, sibling: null, return: null};
const c2 = {name: 'c2', child: null, sibling: null, return: null};
const d1 = {name: 'd1', child: null, sibling: null, return: null};
const d2 = {name: 'd2', child: null, sibling: null, return: null};

a1.child = b1;
b1.sibling = b2;
b2.sibling = b3;
b2.child = c1;
b3.child = c2;
c1.child = d1;
d1.sibling = d2;

b1.return = b2.return = b3.return = a1;
c1.return = b2;
d1.return = d2.return = c1;
c2.return = b3;

let nextUnitOfWork = a1;
workLoop();

function workLoop() {
    while (nextUnitOfWork !== null) {
        nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
    }
}

function performUnitOfWork(workInProgress) {
    let next = beginWork(workInProgress);
    if (next === null) {
        next = completeUnitOfWork(workInProgress);
    }
    return next;
}

function beginWork(workInProgress) {
    log('work performed for ' + workInProgress.name);
    return workInProgress.child;
}

function completeUnitOfWork(workInProgress) {
    while (true) {
        let returnFiber = workInProgress.return;
        let siblingFiber = workInProgress.sibling;

        nextUnitOfWork = completeWork(workInProgress);

        if(siblingFiber ! == null) { // If there is a sibling,return it 
            // to perform work for this sibling
            return siblingFiber;
        } else if (returnFiber ! == null) { // If there's no more work in this returnFiber, // continue the loop to complete the returnFiber. workInProgress = returnFiber; continue; } else { // We've reached the root.
            returnnull; }}}function completeWork(workInProgress) {
    log('work completed for ' + workInProgress.name);
    return null;
}

function log(message) {
  let node = document.createElement('div');
  node.textContent = message;
  document.body.appendChild(node);
}
Copy the code

Let’s take a look at the formUnitofwork and beginWork functions:

function performUnitOfWork(workInProgress) {
    let next = beginWork(workInProgress);
    if (next === null) {
        next = completeUnitOfWork(workInProgress);
    }
    return next;
}

function beginWork(workInProgress) {
    console.log('work performed for ' + workInProgress.name);
    return workInProgress.child;
}
Copy the code

The performUnitOfWork function takes a fiber node from the workInProgress Tree as input and begins work by calling the beginWork function. React will start in the beginWork function to complete all the work that needs to be done for a Fiber node. To simplify the demonstration, I’ll assume that the work I need to do is: Print the name of the current Fiber node. The beginWork function returns either a reference to the fiber node to be processed by the next work loop, or null.

If there is the next child fiber node to be processed, the fiber node will assign it to nextUnitOfWork in the workLoop function. Otherwise, React knows it has reached the leaf of the current branch of the Fiber node tree. React can therefore end the work of the current Fiber node. Once a Fiber node is terminated, React then performs the work of its Sibling node, and after completing this branch of the sibling, moves to the next sibling node….. And so on. React y will trace back to the parent node when all sibling nodes are terminated. This happens in the completeUnitOfWork function:

function completeUnitOfWork(workInProgress) {
    while (true) {
        let returnFiber = workInProgress.return;
        let siblingFiber = workInProgress.sibling;

        nextUnitOfWork = completeWork(workInProgress);

        if(siblingFiber ! == null) { // If there is a sibling,return it
            // to perform work for this sibling
            return siblingFiber;
        } else if (returnFiber ! == null) { // If there is no more workin this returnFiber,
            // continue the loop to complete the parent.
            workInProgress = returnFiber;
            continue;
        } else {
            // We have reached the root.
            returnnull; }}}function completeWork(workInProgress) {
    console.log('work completed for ' + workInProgress.name);
    return null;
}
Copy the code

As you can see, the function implementation body of the completeUnitOfWork is a big while loop. The React implementation enters this function when the workInProgress node has no children. After completing the work of the current Fiber node, it then checks to see if there is another Sibling node to process. If so, return the reference to the next Sibling and exit the current function. The returned sibling reference is assigned to the nextUnitOfWork variable, and React starts the new walk from that Sibling. It is worth emphasizing that React only completes the work of the previous Sibling node, it does not complete the work of the parent node. A Fiber node is said to have completed its work only when work has been done on both its own and all its child branches, and then we can trace back.

As you can see, performUnitOfWork and completeUnitOfWork are primarily used to iterate over the Fiber node tree. The specific things that need to be done in the iteration are accomplished by the beginWork and completeWork. In the next articles in this series, we’ll learn what happens to the ClickCounter and SPAN components as React executes to the beginWork and completeWork functions.

Commit phase

This phase starts with the completeRoot function. In this function, React performs DOM updates and calls to the pre-mutaion and post-mutation lifecycle methods.

After entering the COMMIT phase, React deals with three data structure objects: two trees and a list. The two trees are the Current Tree and the workInProgress Tree (or finishedWork tree), and the one list is the Effect List. Current Tree represents the current state of the application that has been rendered in the user interface. WorkInProgress Tree is the alternate of Current Tree built by React in render phase. It represents the application state that needs to be flushed into the user interface. The workInProgress tree is connected to the Child and Sibling fields of the Fiber node.

The Effect List is a subset of the finishedWork Tree. It links itself via the nextEffect field of the Fiber node. Again, the Effect List is a product of the Render phase. All the Render phase does is figure out which nodes need to be inserted, which nodes need to be updated, which nodes need to be removed, and which component’s lifecycle methods need to be called. That’s what the Effect List tells us. The Fiber nodes on the Effect list are the ones that need to be traversed during the COMMIT phase.

– When debugging, you can access the Current tree from fiber root’s current property. You can access the corresponding Fiber node on the finishedWork Tree using the HostFiber node alternate property. See Root of the Fiber Tree for details.

The primary functionality of the COMMIT phase is commitRoot. It does the following, so to speak:

  • withsnapshotFiber node for effectgetSnapshotBeforeUpdateLifecycle approach
  • withDeletionFiber node for effectcomponentWillUnmountLifecycle approach
  • Perform all DOM operations: node insertion, node update, node deletion
  • thecurrentPointer tofinishedWork tree(In javascript, reference passing).
  • withPlacementFiber node for effectcomponentDidMountLifecycle approach
  • withUpdateFiber node for effectcomponentDidUpdateLifecycle approach

After calling the pre-mutation method getSnapshotBeforeUpdate, React commits all effects on the tree. This operation is divided into two steps. The first step is to perform all DOM node insertions, updates, deletes, and uninstalls of refs. React then assigns the finishedWork Tree to the FiberRoot node’s Current property to convert the finishedWork Tree to current Tree. This is done after the first pass of the commit phase, so that the previous tree is still current during componentWillUnmount, but before the second pass, so that the finished work is current during componentDidMount/Update. Step 2: React calls all other lifecycle methods and ref callback. These methods are executed in a single step. At this point, all the placements, updates, and deletions in the tree have been made.

Here are the two execution steps mentioned above in the commitRoot function:

function commitRoot(root, finishedWork) {
    commitBeforeMutationLifecycles()
    commitAllHostEffects();
    root.current = finishedWork;
    commitAllLifeCycles();
}
Copy the code

All of these child functions iterate over the Effect list. During the iteration, they both check whether the current Fiber Node effect is of the type that this function needs to handle, and if so, apply it.

Pre-mutation lifecycle methods

Here is a small example of code that iterates through the Effect List and checks whether the current Fiber node’s effect type is Snapshot:

function commitBeforeMutationLifecycles() {
    while(nextEffect ! == null) { const effectTag = nextEffect.effectTag;if(effectTag & Snapshot) { const current = nextEffect.alternate; commitBeforeMutationLifeCycles(current, nextEffect); } nextEffect = nextEffect.nextEffect; }}Copy the code

For a class Component, “applying this Snapshot” effect “is equivalent to” calling the getSnapshotBeforeUpdate lifecycle method “;

DOM updates

React does all of the DOM manipulation in the Commitallects function. This function lists the types and operations of DOM operations:

unction commitAllHostEffects() {
    switch (primaryEffectTag) {
        casePlacement: { commitPlacement(nextEffect); . }casePlacementAndUpdate: { commitPlacement(nextEffect); commitWork(current, nextEffect); . }caseUpdate: { commitWork(current, nextEffect); . }caseDeletion: { commitDeletion(nextEffect); . }}}Copy the code

The interesting thing is that React classifies calls to the componentWillUnmount method into deletion, and ultimately calls it in the commitDeletion function.

Post-mutation lifecycle methods

That leaves the life cycle methods componentDidUpdate and componentDidMount. They are called in commitAllLifecycles.

conclusion

Finally, finally, we’re done. If you have an opinion on this article or a question, please feel free to comment in the comments. Also welcome to my next post: In-depth updates to React state and props. I have a lot of articles I’m working on in the in-depth XXX series that I’m going to finish. These articles cover topics such as scheduler, Children Reconciliation, and How the Effects List is Built. At the same time, I also plan to post a video explaining debug based on the content of this article. Look forward to it.