基于React理解虚拟DOM(Virtual DOM)和Diff

2020-06-19  本文已影响0人  雪燃归来

       我在之前的文章《虚拟DOM(Virtual DOM)中动态更新视图的diff算法》中,基于vue描述Virtual DOM(下面可以称为vm)和Diff算法。由于vue2.X中的vm是建立在第三方库snabbdom之上的,所以我们通过分析了snabbdom的关键源码片段讲述了其中几个重要方法。如createElement、render、patch等,如果你感兴趣,可以点击文中的链接进行阅读。
       在react中,vmdiff算法有很大的相似之处,本篇文章主要从逻辑上进行描述,并不深入源码去分析。
       首先,我们需要清楚一个概念,那就是render函数的执行时机。简单一句话就可以概括。

当组件的state或者props发生改变的时候,render函数就会重新执行

一、几种视图渲染的设计方案

       下面是一个很简单的React组件的代码。从代码中我们可以看出,React组件有state(数据)JSX模版组成。那么要从数据+模版到最终生成页面的DOM,怎么样设计才是最高效、性能最有的技术方案呢。

import React, { Component } from 'react'

class TodoItem extends Component{
    constructor(props){
        super(props)
    }
    
    render(){
        return (<li onClick={this.handleClick.bind(this)}>
                {this.props.content}
        </li>)
    }
    handleClick(){
        this.props.deleteItem(this.props.index)
    }
}

export default TodoItem

1、直接渲染成真实DOM

       这是一种最简单,最直接,也是最容易被想到的一种方法。具体的流程如下:
       1、state数据
       2、JSX模版
       3、数据+模版 结合,生成真实DOM来显示
       4、state发生改变
       5、数据 + 模版 结合,生成真实DOM,替换原来的DOM

缺陷
       1、第一次生成一个完整的DOM片段
       2、第二次生成一个完整的DOM片段
       3、第二次的DOM替换第一次的DOM,非常耗性能

2、对比文档碎片(DocumentFragment)修改已发生改变的DOM片段

       这种方法是通过对比前后两次生成DOM的不同(对比时,第二次生成的文档碎片),来修改仅仅有发生改动的DOM片段。具体的流程如下:
       1、state数据
       2、JSX模版
       3、数据+模版 结合,生成真实DOM来显示
       4、state发生改变
       5、数据+模版 结合,生成真实DOM,并不直接替换原始的DOM
       6、新的DOM(DocumentFragment)和原始的DOM做对比,找差异
       7、找到变化(如input的值
       8、只用新的DOM中的input元素,替换掉老的DOM中的input元素

缺陷
       虽然减少了DOM更新的体积,但是增加了DOM比较的操作,所以性能提升不明显

3、采用虚拟DOM和Diff算法

       这种方法是reactvue所采用的渲染方法,主要是通过用JS对象模拟原生DOM,进行一系列的对比、更新操作。由于JS的执行效率很高,再加上Diff算法的巧妙设计,使得这种方法效率也非常高,已成为前端世界中的明星。具体的步骤如下:
       1、state数据
       2、JSX模版
       3、生成虚拟DOM(虚拟DOM就是一个JS对象,用它来描述真实DOM)(\color{red}{损耗性能}

['div',{id: 'abc'},['span', {}, 'hello world']]

       4、用虚拟DOM生成真实DOM,来显示。

<div id="abc"><span>hello world</span></div>

       5、state发生改变
       6、数据+模版 生成新的虚拟DOM(\color{green}{极大提升性能}
       7、比较原始虚拟DOM和新的虚拟DOM的区别,找到区别是span中的内容(\color{green}{极大提升性能}
       8、直接操作DOM,改变span中的内容

       我们需要注意下面几点
       1、JSX并不是虚拟DOM,只是模版,让我们编码更加简洁。
       2、render函数的作用主要是将虚拟DOM渲染成真实DOM,可以直接写html标签,也可以写虚拟DOM
html标签

render(){
    return <div><span>item</span></div>
}

虚拟DOM

render(){
   return React.createElement('div', {}, React.createElement('span', {}, 'item'))
}

优点
       1、性能提升了。
       2、它使得跨端应用得以实现。React Native。vm在浏览器端生成DOM,在原生端生成对应平台所需要的组件。

二、虚拟DOM中的Diff算法

       React中vm中的diff算法跟vue中diff算法原理相同。我们将从下面三点具体看一下Diff算法。

1、React中异步更新数据

       在React中,我们不能直接对state中的数据进行操作,必须通过this.setState```这个方法进行操作,那么这样做有好处吗?肯定是有的。React 底层将改变多次state的改变合并为一次state的改变,减少了更新操作,提升了性能

React 底层将改变多次state的改变合并为一次state的改变

2、前后虚拟DOM对比时采用“同级比较”

       这个对比的方式跟vue中一致。从第一层开始对比,如果第一层没有改变(key和$el),则进行第二层的对比,依次往下。如果第一层发生了改变,直接用新的节点替换旧的节点。


前后虚拟DOM对比时采用“同级比较”

3、列表数据渲染时key值要固定

       在列表渲染的时候,我们要为每一条数据指定唯一的key值,并且这个key值要固定。如果没有key或者key值不确定的情况下,在diff算法前后对比的过程中,前后节点对照关系将不明确。无法做到只修改已经改变的节点,不改变没有发生变化的节点。所以我们不推荐使用循环时的索引值index来做key值。

为每一条数据指定唯一的key值,并且这个key值要固定

       至此,文章结束,感谢您的阅读。

上一篇下一篇

猜你喜欢

热点阅读