关于前端:深入vue中key的作用diff算法下dom节点的高效复用

32次阅读

共计 2921 个字符,预计需要花费 8 分钟才能阅读完成。

我第一次接触 key,是在学习 v -for 循环时,不加 key 标识会报错,对于初学者来说,听过 key 的原理也会懵懵懂懂,只知其一; 不知其二。
当我用了很久的 vue,在回头看 key 的原理时,才彻底弄懂了 key 的原理和作用。

虚构 Dom 渲染

要想理解 key 的原理,绕不开的就是虚构 dom 的渲染过程,因为 key 最大的作用就是标识节点,以便雷同的节点能够被高效复用。

在 vue 渲染过程中,vue 会学生成虚构 dom,也就是用 JavaScript 中的对象残缺形容实在的 dom 节点,生成的虚构 dom 相似如下:

[
    {
        tagName: 'div',
        children: [
            {
                tagName: 'p',
                props: {class: 'row'}
                // ...
            }
        ],
        props: {id: 'app'}
        // ...
    }
]

生成虚构 dom 后,再通过虚构 dom 渲染出实在节点,页面便有了,此时页面存在实在 dom 节点和虚构 dom 节点两局部。

当你批改了 dom 节点后,虚构 dom 会从新生成,此时页面有新旧两个虚构 dom 树,vue 会比拟两个虚构 dom 对象,把完全相同的虚构 dom 对应的实在 dom 节点间接复用,不同的局部进行实在 dom 更新,这个比拟的过程就是所谓的 diff 算法,这样解决使得实在的 dom 的更新最小化,从而使得性能更优。

key 的用途

在比拟虚构 dom 的过程中,如果没有 key,vue 会采纳就地复用的准则,也就是按程序来比拟节点,比方:

当你插入一条数据时

    <ul>
        旧节点          新节点
        <li>1</li>      <li>1</li>
        <li>2</li>      <li>5</li> 
        <li>3</li>      <li>2</li> 
        <li>4</li>      <li>3</li> 
                        <li>4</li> 
    </ul>

新节点 li-1 和旧节点 li-1 比拟,完全一致,不生成新 dom 节点,间接复用旧 dom 节点;

新节点 li-5 和旧节点 li-2 比拟,li 一样,内容不一样,复用 li 节点,文本节点 5 从新生成并替换旧的文本节点2

新节点 li-4 找不到旧的节点比照,间接创立新的 dom 节点

以此类推,如果 li 里不是简略的数字,而是其余简单的组件,那么操作 dom 的老本就更高了。

如何让 vue 在 diff 时晓得新节点和哪些旧节点去比拟呢?那就是 key 起的作用,如果给节点绑定了 key 属性,那 vue 在 diff 的时候,会找到与新节点的 key 雷同的旧节点进行比拟,比方咱们渲染了一页新闻列表:

let list = [
    {
        id: 1,
        title: '新闻标题 1'
    },
    {
        id: 2,
        title: '新闻标题 2'
    },
    {
        id: 3,
        title: '新闻标题 3'
    }
]
<ul>
    <li v-for="item in list" :key="item.id">
        {{item.title}}
    </li>
</ul>

当你插入一条数据在第一条,失去新旧节点如下:

    <ul>
        旧节点                          新节点
                                        <li key="4"> 新闻标题 4 </li>
        <li key="1"> 新闻标题 1 </li>      <li key="1"> 新闻标题 1 </li> 
        <li key="2"> 新闻标题 2 </li>      <li key="2"> 新闻标题 2 </li> 
        <li key="3"> 新闻标题 3 </li>      <li key="3"> 新闻标题 3 </li> 
    </ul>

vue 依据 key 去匹配节点,新闻的 id 是不变的,所以找到旧的对应节点下的内容也是统一,此时,只须要生成 <li> 新闻标题 4 </li>,并在对应的地位插入即可,这样的 dom 更新效率更高

在 for 循环中,能够应用 index 作为 key,然而这会导致数据在插入、删除等毁坏列表程序的操作时,造成效率底下,应用 index 当 key 和不加 key 的成果是始终的,因为当插入一条数据时,节点的 key 也会跟着扭转,还是用新闻列表举例:

当应用 index 作为 key 时,新旧节点 key 的变动和比照关系:

        <ul>
        旧节点                          新节点
        <li key="0"> 新闻标题 1 </li>      <li key="0"> 新闻标题 4 </li>
        <li key="1"> 新闻标题 2 </li>      <li key="1"> 新闻标题 1 </li> 
        <li key="2"> 新闻标题 3 </li>      <li key="2"> 新闻标题 2 </li> 
                                        <li key="3"> 新闻标题 3 </li> 
    </ul>

你会发现,key 相等的节点下的内容简直都不统一,这就造成了大规模节点的抛弃和从新渲染。

key 谬误的经典案例

在网上很多解说 key 作用的案例中,都会用到 <input /> 作用案例,大略逻辑如下:

v-for 渲染一个列表,应用 index 作为 key,每个列表下都会有一个<input/>

let list = [
    {name: '姓名'},
    {name: '年龄'}
]
<div v-for="(item,index) in list" :key="index">
   {{item.name}}:<input />
</div>

当列表被插入一个新值时,input 并不会追随原来的 item,比方在姓名前面的 <input /> 输出了内容,当 list 在第一位插入一个班级时,本来在姓名 <input /> 的内容变成了班级 <input /> 的内容了

这是因为 <input /> 的值在输入框中是长期 DOM 状态,这个状态追随的是 key,所以才会呈现这种状况,然而理论写代码很少会呈现这种状况,因为写 <input /> 个别是为了获取输出,必定会通过 v-model 来绑定值的,一旦绑定了值,<input />中的值就不是长期 DOM 状态,那就不会呈现下面那种状况了。

key 的其余用处 – 更新 key 实现组件从新渲染

key 有个不太正规,但某些状况十分好用的性能,比方有些组件,在创立的时候有初始化的行为:

export default {
    // ...
    data() {
        return {_width: 0}
    },
    props: {
        width: {type: [Number,String]
        }
    },
    created() {if (typeof this.width === 'number') {this._widht = this.width + 'px'} else {this._widht = this.width}
    }
}

上述的代码是一个简略的列子,宽度能够传数字或字符串类型,如果传的是数字类型,创立组件的时候就拼接上 px(该例子能够应用计算属性实现,此处为了展现)。

相似的组件,在 props 批改的时候,_width 并不会被更新,除非监听 width 属性。

如果组件的生命周期里还写了很多依赖 props 的逻辑代码,那不如让组件从新创立好了。

利用 key,就能够轻易触发组件的从新创立

    <my-component :key="key" :width="width"></my-component>
    <button @click="changeWidth"> 批改组件宽度 </button>
    export default {data() {
            return {
                width:  100,
                key: 1
            }
        },
        methods: {changeWidth() {
                this.width += 100
                this.key++
            }
        }
    }

这样,在每次批改 width 时,组件都会从新被创立。

尽管达到了从新执行生命周期的目标,但就义了从新创立组件的性能,所以咱们在设计组件的时候,应多思考 props 被改变的状况(比方用计算属性代替 created 的执行),能力写出一个受欢迎的组件。

正文完
 0