How to understand the Virtual DOM

What is a virtual DOM

Next, use vdom (Virtual DOM) be referred to as a virtual DOM.

Refers to the use of analog JS DOM structure, the change in contrast DOM do JS layer. In other words, the virtual DOM is JS object. 
DOM structure is as follows:
<ul id="list">
    <li class="item">Item1</li>
    <li class="item">Item2</li>
</ul>

Mapped to a virtual DOM is this:

{
    tag: "ul",
    attrs: {
        id:&emsp;"list"
    },
    children: [
        {
            tag: "li",
            attrs: { className: "item" },
            children: ["Item1"]
        }, {
            tag: "li",
            attrs: { className: "item" },
            children: ["Item2"]
        }
    ]
}

React go to call render () method to re-render the entire UI components, but if we really to operate such a large number of DOM, performance is obviously worrying. So React implements a Virtual DOM, there is a mapping relationship between the real and Virtual DOM DOM structure components, React implements a diff algorithm on virtual DOM, when the render () to re-render the assembly time, diff will need to find DOM changes, then put on a real update to modify the browser DOM above, therefore, React not render the whole DOM tree, Virtual DOM that is JS data structure, so in theory much faster DOM native.

Why use virtual dom

An existing example:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>eg</title>
</head>

<body>
  <div id="box"></div>
  <button id="btn">点击</button>

  <Script the src = " https://cdn.bootcss.com/jquery/3.2.0/jquery.js " > </ Script> 
  <Script> const Data = [ ' ha ' , ' Oh ' , ' Hey ' ]
     / / rendering function     function the render (Data) {
       const $ Box = $ ( ' #box ' ); 
      $ box.html ( '' );
       const $ UL = $ ( ' <UL> ' );
       // redraw time 
      $ UL .append ($ ( ' <li>10</li>'
    
)); 
      Data.forEach (Item => {
         // each entry are redrawn 
        $ ul.append ($ ( `<Li> Item $ {} </ Li> `)) 
      }) 
      $ box.append (UL $ ); 
    } 

    $ ( ' #btn ' ) .click (function () { 
      Data [ . 1 ] = Data [ . 1 ] + ' Hey ' ; 
      the render (Data); 
    }); 
    the render (Data)
   </ Script> 
</ body > 
</ HTML>
After clicking this button, there will be a corresponding change in the view, but you can review the following elements, each change, ul labels had to re-create, that ul each of the following sections, regardless of whether the data is the same as the original, had to re rendering, this is not the ideal situation, when one of a column of data, like the original, we hope that this column do not re-rendered, because a considerable consumption of redrawing DOM browser performance.
 
Therefore, we simulated objects JS method, the comparison operation on the DOM JS layer, reducing unnecessary redrawing browser, to improve efficiency.
 
Of course, some people say the virtual than the real DOM DOM is not fast, in fact, is justified. When the above-ul each piece of data has changed, obviously true DOM operations faster because the virtual DOM js than for diff algorithms process there. Therefore the data, applies only to the above-described performance advantages of large rendering data and changes only a small portion of the case
virtual dom basic steps

① build a virtual objects using JS DOM tree, and then build a DOM tree with virtual real tree, and then inserted into the document.
② When the state changes, re-construct a new object tree, old trees and new trees are compared, differences in recording two trees.
③ The difference in step 2 is applied to the real DOM tree constructed in step 1, the view is updated.

Virtual DOM advantages are:

1, a UI functional programming, i.e., UI = f (data) which construct the UI model.

2, JS objects can be rendered to the environment outside the browser DOM, which is supported by a cross-platform development, such as ReactNative.

diff algorithms

React performance virtual dom of good can not do without its own special diff algorithms. Traditional diff algorithm time complexity of reach o (n3), and the diff algorithm time complexity react only o (n), react the diff can be reduced to o (n) relies on the three strategies react diff.

Contrast react diff diff tradition
traditional diff algorithms pursuit of "complete" and "minimal", and react diff is to give up the pursuit of these two:
In the traditional diff algorithm, before and after comparison of two nodes, if a node changes found , the child node will continue to compare nodes, layer by layer to contrast. So to compare the recursive loop, complexity reached o (n3), n is the number of nodes in the tree, imagine if this tree has 1,000 nodes, we have to execute on billions of comparison, this magnitude comparison frequency to basically use the counts do seconds. Then react exactly how to reduce the complexity of o (n) of it?

React diff three strategies
strategy a (tree diff): Web UI across nodes in the DOM hierarchy moving operation is particularly small, negligible. (DOM structure changes ----- directly uninstall and re-creat)
Strategy II (component diff): Like DOM structure ----- does not uninstall, but will update
Strategy three (element diff): All the same sub-levels nodes. they can be distinguished by 1.2 points while following key -----

Virtual DOM tree hierarchy comparison (tree diff)
two trees only on the same level nodes are compared, ignoring cross-level mobile operating DOM node. React DOM node will compare the color within the same block, i.e., all sub-nodes under the same parent node. When the discovery node does not exist, then the node and its children will be completely removed and will not be used for further comparison. In this way only once the tree traversal, we will be able to complete the comparison of the entire DOM tree. As a result the most direct is to enhance the complexity of the changes to the original linear growth rather than exponential growth.

Diff

But if there was cross-level DOM node operation, diff will be how to deal with?

Cross-tier mobile node

When moving on to FIG example, A node and its child nodes linked to another lower DOM, React wit judged not only subtree has moved, but will directly destroyed, and re-create the sub-tree, and then hung on to the destination DOM. In fact, React official does not recommend us to make cross-tier Sao operation. So we can get to realize a truth: that in the realization of our own components, a stable DOM structure will help us to enhance performance.

Comparison between components (component diff)
access to a lot of information online, found written are more difficult to understand, according to my own understanding, in fact, the core of the strategy is to see whether the structure changes. React is based on the components to build applications for the strategy adopted by the comparison between the components is very simple and efficient.
If the same types of components, proceed according to the original Virtual DOM compare strategies.
If not the same type of component, it is determined that the dirty component, thereby replacing all child nodes of the entire monovalent group.
If the same types of components, it is possible to go through a Virtual DOM more down, and has not changed. If we can know in advance exactly that, then you can save a lot of time diff operation. Thus, React allows the user shouldComponentUpdate () to determine whether the component needs to be diff Algorithm.

Component Comparison

As shown above, when the unit D becomes G component, even if these two components similar structures, D and G is determined once React types of components are not, will not compare the two structures, but remove component D, re-create the component G and its child nodes. That is, if and when the two components are of different types but structurally similar, in fact, carried out the analysis will affect the performance diff algorithm, but after all, there is a case similar to the DOM tree of different types of components rarely appear in the actual development process, so this extreme factors is difficult to have a significant impact on the actual development process.

Comparison between the elements (element diff)
When the node at the same level, react diff provides three nodes: Insert, delete, move.

operating description
insert The new node does not exist among the old collections, namely the new node will insert operation
mobile The new node is present in the old set, and only the location update will reuse the previous node, the moving operation done (depending on the Key)
delete Old and new nodes in the collection exists, but can not make changes directly to the node reuse, make deletions

 

Simply look at an example:

Removable inserts

看上面的例子,得知,老集合包含节点 A、B、C、D,更新之后的新集合包括节点: B、A、D、C,然后diff算法对新老集合进行差异检测,发现B不等于A,然后就会创建B然后插入,并删除A节点,以此类推,创建并插入 A、D、C,然后移除B、C、D。
但是这些节点其实都没有发生改变,仅仅是位置上发生了变化,却要进行一大堆的繁琐低效的创建插入删除等操作,React说:“这样下去不行的,我们不如。。。”,于是React允许开发者对同一层级的同组子节点增加一个唯一的Key进行标识。

Key的作用

相信大部分刚开始接触react的时候,都看到过这样的警告:

Console Warning

这是由于我们在循环渲染列表时候(map)时候忘记标记key值报的警告,既然是警告,就说明即使没有key的情况下也不会影响程序执行的正确性.其实这个key的存在与否只会影响diff算法的复杂度,也就是说你不加上Key就会像上面的例子一样暴力渲染,加了Key之后,React就可以做出移动的操作了,看例子:

Plus Key

和上面的例子是一样的,只不过每个节点都加上了唯一的key值,通过这个Key值发现新老集合里面其实全部都是相同的元素,只不过位置发生了改变。因此就无需进行节点的创建、插入、删除等操作了,只需要将老集合当中节点的位置进行移动就可以了。React给出的diff结果为:B、D不做操作,A、C进行移动操作。react是如何判断谁该移动,谁该不动的呢?

 

react源码逻辑梳理

react会去循环整个新的集合:

①从新集合中取到B,然后去旧集合中判断是否存在相同的B,确认B存在后,再去判断是否要移动:
B在旧集合中的index = 1,有一个游标叫做lastindex。默认lastindex = 0,然后会把旧集合的index和游标作对比来判断是否需要移动,如果index < lastindex ,那么就做移动操作,在这里B的index = 1,不满足于 index < lastindex,所以就不做移动操作,然后游标lastindex更新,取(index, lastindex) 的较大值,这里就是lastindex = 1

②然后遍历到A,A在老集合中的index = 0,此时的游标lastindex = 1,满足index < lastindex,所以对A需要移动到对应的位置,此时lastindex = max(index, lastindex) = 1

③然后遍历到D,D在老集合中的index = 3,此时游标lastindex = 1,不满足index < lastindex,所以D保持不动。lastindex = max(index, lastindex) = 3

④然后遍历到C,C在老集合中的index = 2,此时游标lastindex = 3,满足 index < lastindex,所以C移动到对应位置。C之后没有节点了,diff就结束了

以上主要分析新老集合中节点相同但位置不同的情景,仅对节点进行位置移动的情况,如果新集合中有新加入的节点且老集合存在需要删除的节点,那么 React diff 又是如何对比运作的呢?

Both deleted, there are added

和第一种情景基本是一致的,react还是去循环整个新的集合:
①不赘述了,和上面的第一步是一样的,B不做移动,lastindex = 1

②新集合取得E,发现旧集合中不存在,则创建E并放在新集合对应的位置,lastindex = 1

③遍历到C,不满足index < lastindex,C不动,lastindex = 2

④遍历到A,满足index < lastindex,A移动到对应位置,lastindex = 2

⑤当完成新集合中所有节点 diff 时,最后还需要对老集合进行循环遍历,判断是否存在新集合中没有但老集合中仍存在的节点,发现存在这样的节点 D,因此删除节点 D,到此 diff 全部完成

但是 react diff也存在一些问题,和需要优化的地方,看下面的例子:

Local insufficient react-diff

在上面的这个例子,A、B、C、D都没有变化,仅仅是D的位置发生了改变。看上面的图我们就知道react并没有把D的位置移动到头部,而是把 A、B、C分别移动到D的后面了,通过前面的两个例子,我们也大概知道,为什么会发生这样的情况了:
因为D节点在老集合里面的index 是最大的,使得A、B、C三个节点都会 index < lastindex,从而导致A、B、C都会去做移动操作。所以在开发过程中,尽量减少类似将最后一个节点移动到列表首部的操作,当节点数量过大或更新操作过于频繁时,在一定程度上会影响 React 的渲染性能。

三句箴言

所以经过这么一分析react diff的三大策略,我们能够在开发中更加进一步的提高react的渲染效率。
箴言一:在开发组件时,保持稳定的 DOM 结构会有助于性能的提升;
箴言二:使用 shouldComponentUpdate()方法节省diff的开销
箴言三:在开发过程中,尽量减少类似将最后一个节点移动到列表首部的操作,当节点数量过大或更新操作过于频繁时,在一定程度上会影响 React 的渲染性能。

为什么不推荐使用index作为Key

我们再写react的时候,当我们做map循环的时候,当我们没有一个唯一id来标识每一项item的时候,我们可能会选择使用index,官网不推荐我们使用index作为key,通过上面的知识背景,我们其实可以知道为什么使用index会导致一些问题:

看下面的一个场景吧:

    data.map((item, index) => {
        return <li key={index}>{item}</li> 
    })

上面这样写会存在很大的坑,比如看下面的例子:

class App extends Component{
    constructor(props) {
        super(props)
        this.state = {
            list: [{id: 1,val: 'A'}, {id: 2, val: 'B'}, {id: 3, val: 'C'}]
        }
    }

    click() {
        this.state.list.reverse()
        this.setState({})
    }
    render() {
        return (
            <ul>
                {
                    this.state.list.map((item, index) => {
                        return (
                            <Li key={index} val={item.val}></Li>
                        )
                    })
                }
                 <button onClick={this.click.bind(this)}>Reverse</button>
            </ul>
        )
    }
}

class Li extends Component{
    constructor(props) {
        super(props)
    }
    componentDidMount() {
        console.log('===mount===')
    }
    componentWillUpdate() {
        console.log('===update====')
    }
    render() {
        return (
            <li>
                {this.props.val}
                <input type="text"></input>
            </li>
        )
    }
}

Renderings

我们在三个输入框里面,依次输入1,2,3,点击Reverse按钮,按照我们的预期,这时候页面应该渲染成3,2,1,但是实际上,顺序依然还是1,2,3,再看控制台里面,确实是打印了===update===,证明数据确实是更新了的。那么为什么会发生这种事情,我们可以分析一下:

我们可以看下这个图就明白了:

in

As we said before, react to the old collection will look through key, if there are the same elements, react discover new and old key is the same, he would be considered the same component, so the input value box no flashback. We just need to be good idas keyit can solve this phenomenon. 

There exist a little hidden (performance issues):

When we have deleted the data, add other operations. We traversed the index, will vary, diff algorithms to detect differences in collection of old and new in this case, found key value will change then re-render,
we only need to obediently id(或者其他唯一标识)as key, so it will only key values are changed redrawn, we can solve this performance problem.


Guess you like

Origin www.cnblogs.com/gaoht/p/11417481.html