React-hooks: React-hooks: React-hooks: React-hooks: React-hooks: React-hooks: React-hooks: React-hooks: React-hooks: React-hooks: React-hooks: React-hooks But maybe like me, I’m always late, so without further ado, let’s move on:

preface

The React Render function generates a new Virtual DOM Tree each time it is executed. The Diff algorithm compares the difference between the two Virtual DOM trees generated by the render function. The React Diff algorithm can reduce the time complexity to O(n) (n is the node tree in the tree) compared to the O(n^3) time complexity that compares the difference between two random trees using the traditional DIff method.

React uses the diff algorithm to find the smallest set of differences between old and new Virtual DOM trees. The next step is to update these changes to real DOM nodes in a minimum of steps. React describes the reconciliation process as constructing a new Virtual DOM, performing the diff algorithm, updating the old Virtual DOM, and updating the real DOM.

Here’s the component lifecycle diagram after the Render function is executed (to help you understand it) :

React Element

React Elements come in two types: DOM elements and Component elements.

DOM Elements: DOM elements (div, P, etc.) that are natively supported by browsers;

Component Element: An Element built from the React Component;

Virtual DOM

Take Act15 as an example:

// virtual dom const vDom = <div>virtual dom</div> typeof: Symbol(react.element), key: null, ref: null, type: div, props: { children: 'virtual dom' }, _owner: {... }, _store: {validated: false} }Copy the code

VDomObj attributes:

The diff algorithm

The Diff algorithm described on the React website is based on two assumptions:

1. Different types of elements produce different number of nodes.

2. Different child elements can be stabilized by the key attribute.

In addition, there is a hidden assumption:

1. DOM nodes in the Web UI have very few cross-level operations and can be ignored.

To compare

React diff compares old and new Virtual DOM trees and only Virtual DOM trees at the same level:

The diff algorithm only compares nodes in the same color box. This hierarchical comparison strategy enables the diff breadth to traverse the Virtual DOM Tree once to complete the comparison of the entire Tree.

In actual use of React, cross-level operations are unavoidable, as shown in the following figure:

Scenario: Want to move node A from node B to node R, in this case, the whole process of diff algorithm:

find new R.A -> create R.A -> B.A not exists -> delete B.A

The algorithm does not completely move B.A to R.A, but implements this move through the above operation of adding and then deleting

Reduced number of comparisons

When comparing two Virtual DOM trees, diff algorithm preferentially compares the root nodes of the two trees. If their types are different, the two root nodes and their subordinate subtrees are considered to be completely different. Diff algorithm will not continue to compare the two subtrees recursively, as shown in the figure:

When the diff algorithm detects r1.type! == R2.type, R1 and R2 will be regarded as two different nodes, so the R1 node and R1.A and R1.B will be deleted, and then when the R2 node and R2.A and R2.B are created, the diff algorithm will reduce the number of traversal comparison through this strategy, so as to improve the algorithm performance.

The stability of the DOM

The key attribute of the Virtual DOM is used to indicate whether the node is unique. Diff algorithm can judge whether the old and new Virtual DOM belong to the same instance under the same type according to the key value. If the key value of the old and new Virtual DOM is different, The diff algorithm destroys the old instance and creates a new one. The following code, after switching the key of the Child, destroys the original instance of A and creates a real example of B

import React from 'react'; class Child extends React.Component { constructor(props){ super(props); Console. log(' create instance ${props. Name} '); } conponentWillUnmount(){console.log(' Destroy instance ${this.props. Name} '); } render() {return <div> child </div>}} class Parent extends React.Component {state = {key: 1, name: 'a' } render(){ return ( <div> <button onClick={()=>this.setState({key: 2, name: 'b'})} > switch key < / button > < Child key = {this. State. The key} name = {this. State. The name} / > < / div >)}} / * * * initialization: Print out 'Create instance A' * After clicking the toggle button: Print out 'Destroy Instance A' -> Print out 'Create Instance B' **/Copy the code

Key not only ensures the stability of elements, but also helps diff algorithm to quickly find a specific node in the Virtual DOM list.

In practical applications, a node often contains multiple child nodes. For the DIff algorithm, it can effectively and quickly compare the differences of child node lists before and after changes and update steps to improve the performance of React applications

Abstract and simplify the problem: Given prevList and nextList, the minimum operation steps required to transform prevList into nextList can be as follows

  • INSERT_MARKUP: nextList The prevList does not exist on a node, and the new node needs to be inserted
  • MOVE_EXISTING: nextList has the same type and key as prevList, so reuse the prevList and move it
  • REMOVE_NODE: if the prevList node does not have the same type and key as the prevList node, remove the prevList node from the nextList

UpdateQueue is an array. Each element is a JS object describing the operation. Its properties are defined as follows:

  • Type: operation type, including INSERT_MARKUP, MOVE_EXISTING, and REMOVE_NODE
  • FromIndex: initial prevList position of the node
  • ToIndex: The position of a node in the nextList after it has been moved or inserted
  • MarkupNode: The child node to be inserted, valid only for INSERT_MARKUP

Next, we define a function for each operation:

Const updateQueue = []; Function enqueueInsert(markupNode, toIndex) {updateQueue. Push ({type: INSERT_MARKUP, markupNode, fromIndex: null, toIndex }) }; Function enqueueMove(fromIndex, toIndex) {updatequue. Push ({type: MOVE_EXISTING, fromIndex, toIndex})}; Function enqueueRemove(fromIndex) {updatequue. Push ({type: REMOVE_NODE, fromIndex, toIndex: null})};Copy the code

To quickly find a node from prevList and nextList, turn the node list into an object that holds the key-vNode mapping

Function nodeListToMap(list=[]) {const nodeMap = {}; list.forEach( (vnode, index) => { nodeMap[vnode.key || index] = vnode } ) return nodeMap; }Copy the code

Obtain the two nodes with the same key or index in the two lists before and after the change through nodeMap, and then judge the next update operation (insert, delete, move) according to whether the two nodes are equal. To avoid missing nodes in the two lists, traverse the nodes in the two lists respectively.

1. First iterate through the nodes in nextList and compare them to the prevList nodes.

2. Iterate over the prevList node again and compare it to the nextList node.

With the two steps above, you can convert prevList to nextList, leaving three key questions

  • Calculates the insertion position of the new node in the nextList
  • How to delete an old node that does not exist
  • How do I move old reusable nodes

Before addressing these three issues, understand a few concepts:

  • LastIndex: pervList Position of the rightmost node traversed, initial value is 0
  • NextIndex: The index currently traversed to the node in nextList. The initial value is 0
  • MountIndex: The final position of a node in nextList. The initial value is the prevList position of the node

Set mountIndex of nextNode to nextIndex, and insert nextNode to the corresponding position of nextList according to nextNode.mountIndex:

Function insertNode(nextNode, nextIndex) {nextNode.mountIndex = nextIndex; function insertNode(nextNode, nextIndex) {nextNode.mountIndex = nextIndex; enqueueInsert(nextNode, nextIndex); }Copy the code

Delete operation: delete according to original position of old node in pervList:

Function removeNode(prevNode) {enqueueRemove(prevNode.mountIndex); prevNode.mountIndex = null; }Copy the code

PervNode: prevNode.mountIndex = prevList; pervNode: prevList = prevNode.mountIndex = prevList; pervNode: prevList = prevNode.mountIndex = prevList = prevList; pervNode: prevNode.mountIndex = prevList = prevList;

Function moveNode(prevNode, nextIndex, lastIndex) { if (prevNode.mountIndex < lastIndex) { enqueueMove(prevNode.mountIndex, nextIndex) } }Copy the code

Complete calculation update step code:

Function updateList(prevList, nextList) {const pervMap = nodeListToMap(prevList); const nextMap = nodeListToMap(nextList); let name; Let lastIndex = 0; let nextIndex = 0; NextMap for (name in nextMap) {const nextNode = nextMap[name]; PrevNode const prevNode = prevMap[name]; PrevNode (prevNode) {if (prevNode.type === nextNode.type) {// moveNode(prevNode, nextIndex, lastIndex); // Update lastIndex to make sure it is the right-most position lastIndex = math.max (prevNode.mountIndex, lastIndex); Prevnode.mountindex = nextIndex} else {prevNode.mountIndex = nextIndex} else {prevNode.mountIndex = nextIndex} Make sure it is the right-most position lastIndex = math.max (prevNode.mountIndex, PrevNode removeNode(prevNode) insertNode(nextNode) NextIndex)}} else {// prevNode does not exist, InsertNode (nextNode, nextIndex)} PrevMap for (name in prevMap) {if(! NextMap [name]){removeNode(prevMap[name])}}}Copy the code

React Diff: List diff React Diff: List diff React Diff: List diff React Diff

Why not update the changes directly to the real DOM node?

The meaning of updating node changes to real nodes via reconciliation is:

  • Diff algorithm can be used to compare the Virtual DOM before and after render to obtain the minimum set of Virtual DOM that needs to be changed and the minimum steps to update the changed parts to real nodes.
  • During the process of updating the real node, React executes all the steps in a single event loop based on the update steps obtained by Diff and ensures that only one redraw and rearrangement of the real node is performed in this event loop.

The above points ensure that React avoids unnecessary rearrangements and redraws during real DOM updates, thus reducing the performance impact of updates.