摘要
本文结合源码说明了key的作用,给出了一个不写key的反例,并分析了原因。
内容
-
key的作用
-
不写key会出的bug及原因分析
-
key的实践方案
key的作用
在数据变化前后,vue会得到两个虚拟dom树,并依次比较两个虚拟dom树中哪些结点有变动,从而决定去做具体的更新。这里有一个操作要做:比较两个结点是不是同一结点,这个操作在源码是通过sameVnode来完成的。见如下代码。
// https://cdn.bootcdn.net/ajax/libs/vue/2.6.11/vue.js function sameVnode (a, b) { return ( a.key === b.key && ( ( a.tag === b.tag && a.isComment === b.isComment && isDef(a.data) === isDef(b.data) && sameInputType(a, b) ) || ( isTrue(a.isAsyncPlaceholder) && a.asyncFactory === b.asyncFactory && isUndef(b.asyncFactory.error) ) ) ) }
假设我们给一组元素中的每一项都设置了一个唯一的标识,则a.key===b.key
就可以更快地返回结果,这就是提升性能的关键点。
在实践中,渲染一组列表时,key往往是唯一标识(可能其它的条件是相同的),所以,如果不定义key,vue只能认为比较的两个节点是同一个 (undefined===undefined),哪怕它们实际上不是,这导致了频繁更新元素,使得整个patch过程比较低效,影响性能。有些特殊的时候甚至会有一些bug。
bug来了
下面这一段代码是硬编出来的,用它来说明如果在v-for循环中不写key,或者key没有正确使用会带来的后果。
<div id="app" style="padding:20px"> <coma v-for="(it,idx) in list" :i="idx" @click="d">{{it}}</coma> </div> <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.3/vue.js"></script> <script> Vue.component('coma',{ data() { return { v: Math.random() } }, template: ` <div @click="$emit('click',i)"> <slot></slot>-<span>{{v}}</span> <button>点我删除</button> </div> `, props: ['i'] }) new Vue({ el: "#app", data: { list: [1, 2,3,4,5] }, methods: { d(idx) { this.list.splice(idx, 1) } } }) </script>
bug描述:当在某一项上点击删除时,它会删除最后一项,而不是删除当前项(请把注意力放在最后一项)。
原因分析
vue是通过虚拟dom来表示描述真实dom的,在去更新视图之前,要对前后两个虚拟dom树进行分析,以得出它们的区别。如果不设置key,Vue 会使用一种最大限度减少动态元素并且尽可能的尝试就地修改/复用相同类型元素的算法。而使用 key 时,它会基于 key 的变化重新排列元素顺序,并且会移除 key 不存在的元素。看官网 https://cn.vuejs.org/v2/api/#key
分析一下上面bug, 以在第2项上点击为例。
期待的情况,在点击前的数据:
1,2,3
在点击之后的数据
1,3 // 第二项被删除了。
但是由于没有设置key,此间产生的变化是:
-
把原来的第2项中的2改成3
-
把第3项删除掉。
所以会导致序号异常,且最后一项被删除了。
还有一个bug可以看这里https://codesandbox.io/s/vue-v-for-key-50x89。类似的情况还会发生在使用了v-transition时。
推荐设置key
可以通过如下的改进来避免上面的坑:
-
给列表中的数据项添加唯一的id
data: { list: [{id:1,value:1},{id:2,value:3},{id:3,value:3}] },
-
修改视图
<coma v-for="(it,idx) in list" :key="it.id" :i="idx" @click="d">{{it.value}}</coma>
这样,在删除第二项时,vue会做出正确的判断:删除第二个组件。
小结
-
key的作用主要是为了更高效的对比虚拟dom中的某个节点是不是相同节点,是用来提高diff算法的性能表现。更具体一点,vue在patch过程(执行diff的算法,可翻译为打补丁算法)中判断两个节点是否是相同节点,key值相同是一个必要条件。
-
key会提升效率。某些特殊情况下,不写key会出错。
-
在使用v-for循环时,尽量避免直接使用数组的下标为key,因为它们在做删除操作时可能会导致渲染异常。
-
最好是将key设成数据项中的主键:可以把一项与另一项区别开的值。