在写vue的项目中为什么index不能做key使用?

前言
在我们写vue的的项目时,我们是否会疑惑后端数据为什么都会带一个Id,而这个Id一般都作为循环中的key来使用,我们为什么不直接用index来作为他的key呢?这样不是更方便吗?其实不止Vue,React在遇到重复的Key后会直接控制台提示禁止使用重复的Key,React也不推荐使用索引值作为Key。
key的作用
在解决这个疑惑之前,我们先来了解了解key在循环中的作用吧。
1、key是唯一标识,它作用主要是为了更高效的让diff算法
更准确的找到需要被对比的两个结点。
2、Vue在patch过程中判断两个节点是否是相同节点key是必要条件,渲染一组列表时,key是作为唯一标识,所以如果不定义key的话,Vue只能认为比较的两个节点是同一个,导致了频繁更新元素,使得整个patch(diff算法)过程比较低效,影响性能。
3、在实际的使用中在渲染一组列表时key必须设置,而且必须是唯一标识(所以不能用随机数做key),应该避免使用index作为key,因为会导致一些隐蔽的bug;Vue中在使用相同标签元素过渡切换时,也会使用key属性,其目的也是为了让Vue可以区分它们,否则Vue只会替换其内部属性而不会触发过渡效果。
虚拟DOM树
简单来说,虚拟DOM树就是一个对象而已, 其中描述了每一层容器的特征。
其主要是利用 js 描述元素与元素的关系,用 js 对象去表示真实的 DOM 树结构,创建一个虚拟 DOM 对象 以便内在浏览器中操作数据的改变。
vue在组件渲染的时候会在生命周期中调用 render 函数,这个函数也会生成一个虚拟 dom,再根据这个虚拟 dom 生成真实的 dom,然后这个真实的 dom 会挂载到页面中。
如果组件内有响应的数据,数据发生改变的时候 render 函数会生成一个新的虚拟 dom, 新的虚拟 dom 树会去使用diff算法去和旧的虚拟 dom 树进行对比,找到要要修改的虚拟 dom 的部分,去修改相对应部分的真实 dom,在将真实dom挂载到页面中。
diff算法
1.diff算法diff算法就是进行虚拟节点对比,并返回一个patch对象,用来存储两个节点不同的地方,最后用patch记录的消息去局部更新Dom。
2.diff算法就是用 JavaScript 对象结构表示 DOM 树的结构;然后用这个树构建一个真正的 DOM 树,插到页面中。
3.当中当状态变更的时候,diff算法又会重新执行,构造一棵新的虚拟DOM树结构。然后用新的树和旧的树进行比较,记录两棵树差异,同时把新的DOM树所记录的差异应用到旧的DOM树中,然后再构建的真正的DOM树上,实现了视图的更新
index不能作为key的两种demo情况
deom1
<body>
<div id="app">
<ul>
<item v-for="(num, index) in list" :key="num.id" || :key ='id'
:num="num.num" :class="`item${num.num}`"></item>
</ul>
<button @click="change">change</button>
</div>
<script>
new Vue({
el: '#app',
data() {
return {
list: [
{id:1, num:1},
{id:2, num:2},
{id:3, num:3},
]
}
},
methods: {
change(){
this.list.reverse()
}
},
components: {
item: {
props:['num'],
template:`
<div>
{{
num
}}
</div>
},
name:'child'
}
})
</script>
</body>
我们先来说说demo1,我们来看看用index和id做key值其内部的虚拟DOM结构发生的变化。
在这两种DOM树的比较中,我们可以看到用index作为key值时,在列表倒装时,其生成的新的DOM树结构发生了改变,这就导致了diff有要重新去执行一遍,导致性能的浪费。而用id作为key值时,生成的DOM树结构没有发生变化。
所以用index作为key会导致diff 中的优化失效(降低了复用性,违背了虚拟DOM的初衷)
demo2
<body>
<div id="app">
<ul>
<li v-for="(num, index) in list" :key="index">
<item/>
</li>
</ul>
<button @click="del">del</button>
</div>
<script>
new Vue({
el: '#app',
data() {
return {
list: [ 1, 2, 3]
}
},
methods: {
del() {
this.list.splice(0, 1)
}
},
components: {
item: {
template: '<div>{{Math.random()}}</div>'
}
}
})
</script>
</body>
复制代码
在deom2中,当我们要删除列表的第一和值时,如果使用index作为key,会产生严重的bug,同样的,我们先来分析分析他的dom结构。
在我们使用index作为key时,当我们要删除列表的第一个值时,第二个值的key就会向前进一位,而diff算法删除生成的新的DOM树会识别到列表的长度缩短了一位,而直接生成前两两个值的DOM树结构,从而删除了最后一位DOM结点,导致出现bug。而当我们使用id作为key是,diff算法会直接删除对象的key的结点。
所以在删除数据时,因为vue 不会深入的去对比子组件的文本内容,所以会错误移出VDOM中的结点。
解决方案
同时如果后端没有给指定的id时我们也可以使用字符串拼接的方式来设置组件的Key。 例如:
data.map((item,index)=><div key={`demo-article-${index}`}>{item.text}</div>)
这样解决了后端没有返回指定Key的问题又防止组件出现重复的key,当然了这个方法只针对于组件不会删除的情况。如果组件会频繁切换可以使用随机数、时间戳方式来解决,同时如果使用了React18可以使用useId()这个Hooks。
总结
介绍完这两种情形下,在我们平常的conding中,为了使我们能按时下班,我们也应该拒绝用index作为key来使用。