我第一次接触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的执行),能力写出一个受欢迎的组件。