What is the virtual DOM

Virtual DOM is a programming idea, which uses JS objects (tree structure) to simulate the content needed by the real DOM.

Why use the virtual DOM

Before we do that, let’s take a look at how browsers render HTML pages.

The following is the detailed process of webKit engine rendering. Other engines render differently:

Rendering process:

    1. Parsing HTML to Generate A DOM Tree – The rendering engine first parses the HTML document to generate a DOM tree
    1. Parsing HTML through embedded, external chain, embedded CSS styles introduced to generate CSSOM trees
    1. Generate another Render tree from the DOM tree and CSSOM tree for rendering
    1. Render tree – Layout each node of the Render tree to determine its display position on the screen
    1. Render tree – Finally Render each node according to the Render tree

As you can see from the above, redrawing a page can be a performance drain on the browser.

Implementing the virtual DOM

Real DOM

<div class="box"> <p class="para">hello world! </p> <div id="box">box</div> <ul> <li>first</li> <li>second</li> <li>third</li> </ul> </div>Copy the code

React generates the virtual DOM result using the render function:

{
    tag: "div",
    props: {
        class: "box"
    },
    children: [{
        tag: "p",
        props: { class: "para" },
        children: [ "hello world!" ]
    }, {
        tag: "div",
        props: { id: "box" },
        children: [ "box" ]
    }, {
        tag: "ul",
        props: {},
        children: [
            {
                tag: "li",
                props: {},
                children: [ "first" ]
            },
            {
                tag: "li",
                props: {},
                children: [ "second" ]
            },
            {
                tag: "li",
                props: {},
                children: [ "third" ]
            }
        ]
    }]
}
Copy the code

In order to consume less performance, React pushes the changes to a queue each time and generates the virtual DOM according to specific rules. In this case, if there are multiple setstates, React only takes the last setState, thus avoiding the performance cost of generating multiple virtual DOM. Then compare it with the last virtual DOM, at which point the DIff algorithm is introduced.

The complexity of the traditional DIff algorithm is O(n^3), which obviously cannot meet the performance requirements. React converts O(n^3) complexity problems into O(n) complexity problems by making bold strategies.

Diff algorithm optimization strategy in virtual DOM:

  • Peer comparisons, because DOM nodes move across hierarchies well in the Web UI, can be ignored.

  • For two different types of components, different tree structures are generated.

  • For the same component, the virtual DOM is compared to determine which child elements remain stable under different renderings using unique key values at the same level.

  1. At the same level to compare

The diff algorithm only compares elements at the same level, and when it finds differences between levels, it discards its children and replaces the old one with all the children below the new level of difference. Because it is much easier to recreate the child level than to create it layer by layer, especially if the child level has many levels and the structure is complex.

There are three operations to compare two virtual DOM: delete, replace, and update.

VNode is the current virtual DOM, newVNode is the new virtual DOM:

Delete: newVNode does not exist.

Replace: Different keys or types exist between vnodes and newvnodes

Update: Same type and key, but different for VNode and newVNode

  1. Refer to the key value

Both Vue and React warn if no key is assigned to an item in a looping list. Because the key values correspond to two identical elements in the virtual DOM alignment, it is much faster to know which nodes are being added, moved, and removed.

Layer by layer comparison of nodes

Consider the following DOM structure conversions:

Node A is moved completely under node D. Intuitively, DOM Diff operation should be as follows:

A.parent.remove(A); 
D.append(A);
Copy the code

However, React only takes into account the position change of nodes at the same layer, and only creates and deletes nodes at different layers. When the root node finds that A is missing from the child node, it destroys A directly. When D finds that it has an extra child node A, it creates A new child node A. So the actual operation for this structural transformation is:

A.destroy();
A = new A();
A.append(new B());
A.append(new C());
D.append(A);
Copy the code

As you can see, the tree with A as the root node is completely recreated.

In this case, the stability of the DOM structure from the same component helps improve performance. For example, we can sometimes hide or show certain nodes through CSS without actually removing or adding DOM nodes.

DOM Diff and the lifecycle

The React lifecycle is closely related to the DOM Diff algorithm:

  • Constructor: the constructor executed when the component is created

  • ComponentDidMount: Executed when the component is added to the DOM tree

  • ComponentWillUnmount: Executed when the component is removed from the DOM tree. React considers the component destroyed

  • ComponentDidUpdate: Executes when a component is updated

When the DOM tree is transformed as follows. To observe the execution of these methods:

C will unmount.
C is created.
B is updated.
A is updated.
C did mount.
D is updated.
R is updated.
Copy the code

As you can see, the C node is completely rebuilt and then added below the D node, rather than being “moved” there.

Comparison of nodes of the same type

The second kind of node comparison is the same type of node, the algorithm is relatively simple and easy to understand. React resets properties to convert nodes.

renderA: <div id="before" />
renderB: <div id="after" />
=> [replaceAttribute id "after"]
Copy the code

The style attribute of the virtual DOM is slightly different. Its value is not a simple string but must be an object, so the conversion process is as follows:

renderA: <div style={{color: 'red'}} />
renderB: <div style={{fontWeight: 'bold'}} />
=> [removeStyle color], [addStyle font-weight 'bold']
Copy the code

Comparison of list nodes

Operations on list nodes typically include adding, removing, and sorting. For example, we need to insert node F directly into B and C. In jQuery we might use $(B).after(F) to do this. In React, we will only tell React that the new interface should be A-B-F-C-D-E, and Diff algorithm will update the interface.

In this case, if there is no unique identifier for each node, React cannot identify each node. Then the update process will be inefficient. That is, C will be updated to F, D to C, E to D, and finally an E node will be inserted. The effect is shown below:

As you can see, React updates nodes one by one, converting to the target node. The final insertion of the new node E involves a lot of DOM operations. If you give each node a unique key, React can find the correct location to insert new nodes, as shown below:

Adjusting the order of list nodes is also similar to inserting or deleting. Let’s look at the transformation process with the example code below.

That is, the positions of nodes at the same layer are adjusted. React considers the component types after B and C to be different, so delete it completely and rebuild it. Console output is as follows:

B will unmount.
C will unmount.
C is created.
B is created.
C did mount.
B did mount.
A is updated.
R is updated.
Copy the code

If a key is provided, the console outputs the following:

C is updated.
B is updated.
A is updated.
R is updated.
Copy the code

As you can see, providing a unique key attribute for list nodes helps React locate the correct nodes for comparison, which greatly reduces DOM operations and improves performance.

conclusion

By analyzing the three strategies of React Diff, we were able to further improve react rendering efficiency during development.

  • When developing components, maintaining a stable DOM structure helps improve performance;
  • Use the shouldComponentUpdate() method to save diff overhead
  • During development, operations such as moving the last node to the head of the list should be minimized. If the number of nodes is too large or update operations are too frequent, React rendering performance will be affected to some extent.

Refer to the article

The main content of this article comes from the following articles, thank you for your colleagues to write out the theory + practice article, for my understanding of this part of the content provided a lot of help. Special thanks!

React (4) : Virtual DOM Diff algorithm parsing

【React】 In-depth understanding of virtual DOM and diff algorithms

Introduction to virtual DOM

Brief analysis of browser rendering principle

Browser rendering principle and process