1. React — Virtual DOM

What exactly is JSX

It is important to understand what JSX is when we learn about Virtual DOM. JSX looks like HTML, but it is not HTML. JSX is actually JavaScript code, a syntax extension of JavaScript created by the React team to describe the user interface. It was created to make it easier for React developers to describe the user interface in JavaScript, but browsers don’t know JSX and can’t implement it. So Babel compiles JSX code before JSX executes, compiling it into JavaScript code that the browser can execute!

<div className="container">
    <h3>Hello React</h3>
    <p>React is great</p>
</div>
Copy the code

Babel compiled

React.createElement (
    "div",
    {
        className: "container"
    },
    React.createElement("h3".null."Hello React"),
    React.createElement("p".null."React is great"))Copy the code

JSX is just a function for React. CreateElement (Component, props,… Grammar sugar provided by the children) method. This means that all JSX code will eventually be converted to react.createElement (…). Babel helps us through this transformation process.

The first argument to the react. createElement method is the node type, and the value is a string of node names. The second argument is the node property. The third and subsequent arguments are all children of the current node.

The react. createElement method is used to create a Virtual DOM object, which is essentially a JavaScript object. This is an implementation of using JavaScript objects to describe DOM objects. React. CreateElement returns the VirtualDOM. React converts the VirtualDOM into a real DOM object and displays the real DOM on the page. This is the JSX transformation process

Note: Babel determines the initial letter of a component in JSX at compile time. When the initial letter is lowercase, it is considered a native DOM tag. The first variable of createElement is compiled as a string. When the initial letter is uppercase, it is considered a custom component, and the first variable of createElement is compiled as an object.

React developers would be too cumbersome to write the user interface like this.

Can we use Babel REPL to see what JSX conversion js code looks like

DOM manipulation problems

DOM manipulation with scripts is expensive. An apt analogy is to imagine DOM and JavaScript as islands, connected by a toll bridge. Each time JS accesses the DOM, it has to pass through this bridge and pay a “toll fee”. The more times it accesses the DOM, the higher the fee. Therefore, the recommended course of action is to minimize the number of bridge crossings and try to stay on ECMAScript Island. It is essential for modern browsers to operate DOM with JavaScript, but this action is very performance consuming, because it is much slower to operate DOM objects with JavaScript than to operate ordinary objects with JavaScript. If the page is frequently operated with DOM, the page will be jammed and the application fluency will be reduced. Makes for a very bad experience.

Most JavaScript frameworks make this slow operation worse by updating the DOM far more than necessary. For example, if you have a list of items and you change just one item in the list, most JavaScript frameworks will rebuild the entire list, which is ten times more work than necessary. Low efficiency of renewal has become a serious problem

React’s virtual DOM is valuable for this reason. It creates virtual DOM’s and stores them. Whenever the state changes, it creates new virtual nodes to compare with the previous ones and render the changed parts. It has greatly improved the efficiency of JavaScript to manipulate the DOM. What is Virtual DOM

What is the Virtual DOM

A Virtual DOM object is actually a JavaScript object. JavaScript objects are used to describe information about the DOM object, such as what type the DOM object is, what properties it has, and what child elements it has.

You can also think of a Virtual DOM object as a copy of a DOM object, but not directly displayed on the screen

<div className="container">
  <h3>Hello React</h3>
  <p>React is great </p>
</div>
Copy the code

The following is the virtual DOM object. The type attribute represents the type information of the node, the props attribute represents the property information of the node, and the children attribute represents the child node information of the node. The text information for the node is also textContent inside the props property. Can correspond to the above JSX, see the corresponding virtral DOM object basic structure

{
  type: "div".props: { className: "container" },
  children: [{type: "h3".props: null.children: [{type: "text".props: {
            textContent: "Hello React"}}]}, {type: "p".props: null.children: [{type: "text".props: {
            textContent: "React is great"}}]}Copy the code

After understanding Virtual DOM, how does Virtual DOM improve efficiency

Cross-browser compatibility

React implements its own event mechanism based on VitrualDom. It simulates the event bubble and capture process, adopts event proxy, batch update and other methods, and smooths out event compatibility issues in various browsers.

Cross-platform compatibility

VitrualDom brings cross-platform rendering capabilities to React. Take React Native as an example. React draws the UI layer of the platform based on the VitrualDom, but the posture is different for different platforms.

How does Virtual DOM improve efficiency

The core principle is to minimize DOM manipulation, pinpoint the DOM objects that have changed, and update only the parts that have changed.

After React creates a DOM object for the first time, it creates a Virtual DOM object for each DOM object. React updates all Virtual DOM objects before DOM objects are updated. React will compare the updated Virtual DOM to the original one to find out what has changed. React will update the changed part to the real DOM object. React will only update the necessary part. This improves the performance of JavaScript in manipulating the DOM. Manipulating JavaScript objects when manipulating virtualDOM is very fast and almost negligible, because Virtual DOM objects update and compare only in memory and do not render anything in the view, so this part of the performance cost is negligible.

React will delete a node in the entire DOM tree by comparing the virtral DOM before the update with the Virtual DOM after the update. Instead of updating the entire DOM tree, DOM manipulation performance is improved

Before the update JSX

<div id="container">
	<p>Hello React</p>
</div>
Copy the code

The updated JSX

<div id="container">
	<p>Hello Angular</p>
</div>
Copy the code

It can be seen that the text inside the P tag has changed. Let’s see how the virtual DOM is compared

Virtual DOM before update

const before = 
  type: "div".props: { id: "container" },
  children: [{type: "p".props: null.children: [{type: "text".props: { textContent: "Hello React"}}]}Copy the code

The updated virtual DOM

const after = {
  type: "div".props: { id: "container" },
  children: [{type: "p".props: null.children: [{type: "text".props: { textContent: "Hello Angular"}}]}Copy the code

The two virtual DOM’s are compared. It’s easy to see that only the child node’s content changes. React only updates the changed content node to the real DOM, which improves DOM manipulation efficiency.

summary

Now we really know what a Virtual DOM object is, which is actually a JavaScript object, a way of using JavaScript objects to describe real DOM objects, and how it makes manipulating the DOM more efficient, By comparing the new and old Virtual DOM objects to find out the differences, only the differences of DOM objects are finally updated, so as to improve the efficiency of JavaScript DOM manipulation.

2. React — Diff algorithm

React requires two virtual DOM trees to be maintained at the same time: one representing the current DOM structure and the other generated when React state changes are about to be re-rendered. React compares the differences between the two trees to determine if and how the DOM structure needs to be modified. This algorithm is called the Diff algorithm.

There are some general solutions to this algorithmic problem, which generate the minimum operand to convert one tree to another. However, even among the most cutting-edge algorithms, the traditional DIff algorithm uses cyclic recursion to compare nodes in turn, which is O(n^3) complexity and inefficient. Where n is the number of elements in the tree.

If the algorithm was used in React, the amount of computation required to show 1000 elements would be on the order of a billion. The cost is simply too high. To reduce algorithm complexity, React diff presets three limits:

  1. Sibling elements Diff. If a DOM node crosses the hierarchy in two updates, React doesn’t try to reuse it.
  2. Different types of elements create different trees. If an element changes from div to P, React destroys the div and its descendants and creates a new p and its descendants.
  3. Keys can be used to indicate which child elements will remain stable under different renders.

The React Diff algorithm is roughly executed as follows:

Pre-knowledge: Depth-first Traversal (DFS) and Breadth-first Traversal (BFS) of binary trees

Depth-first traversal: Traverses vertically from the root node along the left subtree until the leaf node is found. Then go back to the previous node and traverse the right subtree node until all reachable nodes have been traversed.

Breadth-first traversal: starting from the root node, traverses the layers of the binary tree vertically on the basis of traversing the segments of the binary tree horizontally

Diff algorithm will perform depth-first traversal of the old and new trees to avoid complete comparison between the two trees, so the algorithm complexity can reach O(n). Then generate a unique flag for each node:

In the traversal process, the old and new trees are compared at each node traversal, andOnly elements of the same level are compared:

That is, only the parts of the graph connected by dotted lines are compared and the differences are recorded.

React Diff algorithm

  • Tree diff: Ignore the operation of UI-level DOM nodes across hierarchies. (Small quantity)

  • For component diff: Two components with the same class generate similar tree structures, and two components with different classes generate different property structures.

  • For element structures (element-diff): Use unique IDS (key attributes) for a group of nodes at the same level

(1) Tree diff

Tree Diff only compares nodes at the same level. Because there are few DOM movement operations across hierarchies, the Tree Diff of the React Diff algorithm does not make an in-depth comparison of such operations, but simply deletes and creates them

As shown in the figure, node A (including its children) is moved to node D entirely. React only considers the position change of nodes at the same level, but only creates and deletes nodes at different levels.

  • When the root node finds that A has disappeared in the child node, it destroys A directly. The node and all its children will beCompletely removeNo further comparison is made. When D discovers that A child node A is added, it creates A new child node (including the child node) as its child node. Diff: create A → create B → create C → delete A
  • The comparison of the entire DOM tree is done only once.

It can be seen that when A node is moved across the hierarchy, the imaginary move operation does not occur, but the whole tree with A as the root node is recreated. This is an operation that affects React performance, so it is not recommended to operate across DOM nodes.

For these reasons, maintaining a stable DOM structure when developing components can help improve performance. For example, you can hide or show nodes through CSS without actually removing or adding DOM nodes

(2) Component diff

Component diff is a diff algorithm that compares React components at the same level before and after updates:

  • If it is a component of the same type, continue to compare the Virtual DOM tree (for example, continue to compare the component props with the child nodes and their properties in the component).
  • If not, the component is judged to be a dirty Component and all child nodes under the entire component are replaced, that is, the original component is destroyed and a new component is created.
  • Knowing that it is possible for components of the same type to have no change in their Virtual DOM can save a lot of diff time. As a result, React allows the user to determine if the component needs diff analysis via shouldComponentUpdate()

As shown in the figure, when component D changes to component G, even though the two components have similar structures, React will delete component D and recreate component G and its children instead of comparing their structures once it determines that D and G are different types of components.

Although diff can affect performance when two components are of different types but have similar structures, as the React blog notes, components of different types rarely have similar DOM trees, so this extreme factor is unlikely to have a significant impact on actual development

(3) Element Diff:

Element Diff is a diff algorithm that specializes in all nodes of the same hierarchy, including element and component nodes. When nodes are at the same level, diff provides three node operations: INSERT_MARKUP, MOVE_EXISTING, and REMOVE_NODE.

We call the set of all nodes at the same level to be compared in the virtual DOM tree as the new set and the old set respectively, and the following strategies are adopted:

  • INSERT_MARKUP: A type of component or element node of the new set does not exist in the old set, that is, the new node needs to be inserted into the new node.
  • MOVE_EXISTING: Generatecomponent-children has called receiveComponent, in which case prevChild=nextChild, generatecomponent-children has called receiveComponent, You need to move it around, and you can reuse the old DOM node.
  • REMOVE_NODE: a component or node type of the old collection that exists in the new collection but has different elements that cannot be reused or updated. Therefore, you need to delete the component or node that is not in the new collection.

As shown in the figure, the old set contains nodes A, B, C and D, while the updated new set contains nodes B, A, D and C (only the position changes, the respective nodes and internal data remain unchanged)The old and new sets are diff differentiated in sequence, found B! = A, create and insert B into new set, delete old set A; Similarly, create and insert A, D, and C, and delete B, C, and D.

React finds that such operations are cumbersome and redundant, because these nodes are the same, but the location sequence changes, which requires complex and inefficient deletion and creation operations. In fact, you only need to move the location of these nodes.

React proposes an optimization strategy to address this problem. It allows developers to add unique keys to subnodes of the same hierarchy. See the key mechanism below

3. React — Key mechanism

(1) Functions of Key:

When a node at the same level adds a key attribute that is unique to other nodes at the same level, when its position at the current level changes. After the React Diff algorithm compares the old and new nodes, if the old and new nodes with the same key value are found, the algorithm moves the nodes (and then compares and updates the nodes according to the original policy), instead of deleting the old nodes and creating new nodes as the original policy does. This definitely improves React performance and rendering efficiency

(2) Specific execution process of Key:

First, loop through for (name in nextChildren), judge whether there is the same node in the new set if (prevChild === nextChild) by the unique key, if there is the same node, then move the operation. If (child._mountIndex < lastIndex), the position of the current node in the old set must be compared with that of lastIndex. Otherwise, this operation is not performed.

Example 1: All nodes at the same level only change position:

Begin traversal in the order in the new collection

  1. If B’s lastIndex(similar to a buoy) = 0 in the new set and index = 1 in the old set, index > lastIndex, it is considered that B has no influence on the position of other elements in the set and does not move. And then lastIndex = Max (index, lastIndex) = 1
  2. Index = 0, lastIndex = 1, if index < lastIndex, move A, lastIndex = Max (index, lastIndex) = 1
  3. LastIndex = Max (index, lastIndex) = 3
  4. C and A do the same thing, same as (2), move it, so lastIndex = Max (index, lastIndex) = 3

The move operation in the above conclusion means updating the rendering of the node, while not moving means updating the rendering is not needed

Example 2: All nodes at the same level are added or deleted and their positions change:

  1. In the same case, B doesn’t move, lastIndex is equal to 1
  2. Get E in the new set, find E not in the old set, create E at lastIndex, lastIndex++
  3. If I pick C in the old set, C doesn’t move, lastIndex=2
  4. If I take A from the old set, A moves to the position in the new set, lastIndex=2
  5. After completing diff of all nodes in the new set, the old set is iterated to find nodes (D in this case) that do not exist in the new set and delete node D.

(3) index as key:

React is often used to dynamically generate multiple child nodes in the current hierarchy by traversing (e.g. Array.map). This is a common tabular data rendering scene.

React does not recommend using the traversal index as the key attribute of nodes in this scenario. For example, all nodes currently traversed are of the same type, but their internal texts are different. In the case that index is used as the key, when we change the order of some elements in the original data list, the texts of the old and new nodes corresponding to the same index are inconsistent in the diff comparison between the old and new sets. Some nodes will need to update the rendered text, whereas if other stable unique identifiers are used as keys, only positional order changes will occur, eliminating the need to update the rendered text, improving performance.

In addition, using index as the key can cause some unexpected display errors:

{this.state.data.map((v,index) = > <Item key={index} v={v} />)}
['a','b','c']=>
<ul>
    <li key="0">a <input type="text"/></li>
    <li key="1">b <input type="text"/></li>
    <li key="2">c <input type="text"/></li>
</ul>

// Array rearrangement -> ['c','b','a'] =>
<ul>
    <li key="0">c <input type="text"/></li>
    <li key="1">b <input type="text"/></li>
    <li key="2">a <input type="text"/></li>
</ul>
Copy the code

In the above example, after the array is reordered, the instances corresponding to the key are not destroyed, but updated. Insert key=0; insert key=0;

  • The component is rerendered to get a new virtual DOM.
  • React considers a component to be the same, so only the component can be updated.
  • And then compare their children, found that the content of the text content is different (from a – > c), and input component did not change, then trigger a component componentWillReceiveProps method, so as to update their text content;
  • Because components of children in the input component did not change, its components and father is no connection between the incoming President of props, so the input component does not update (namely the componentWillReceiveProps methods will not be executed), causes the user input values will not change.

(4) Disadvantages of key mechanism:

As shown in the figure, if the nodes of the new set are updated as D, A, B, and C, only D node moves compared with the old set, while A, B, and C still maintain their original order. Theoretically, DIff should only move D. However, since D is the largest in the old set, The _mountIndex of another node is less than lastIndex. As A result, A, B, and C move to the rear of node D instead of node D.

During development, minimize operations like moving the last node to the head of the list. React rendering performance is affected to some extent when the number of nodes is too large or update operations are too frequent.

(5) Precautions for key use:

  1. If the list subsections are iterated over as pure presentation, and do not involve dynamic changes in the order of the list elements, then using index as the key is fine.
  2. Key is only optimized for diff comparison of nodes at the same level, and nodes across the level have no effect on each other’s key values
  3. In most cases, element nodes that use the key attribute at the same level of traversal have the same node type (e.g. span elements or the same component). If the same key exists in the old and new collections, and the node type is different (for example, from SPAN to div), this is equivalent to completely replacing the old node, deleting the old node, and creating the new node.
  4. If there is a key in the new set that did not exist in the old set. For example, a node whose key was 1 now has a key of 100, but no other node in the old collection has a key of 100 either. In this case, the DIff algorithm destroys and recreates the corresponding node. This is useful in some scenarios (such as resetting the state of a component)
  5. The toString() operation is performed on keys before comparison. Therefore, do not use values of object type as keys. This will result in nodes with the same key value in the same hierarchy. Nodes or components of the same type with duplicate keys are likely to copy duplicate internal child elements

Reference links:

Juejin. Cn/post / 697071…

Juejin. Cn/post / 696762…