这个问题涉及到了虚拟Dom的更新过程和策略。当渲染的数据发生改变时,会生成新的虚拟Dom树和老的虚拟Dom树进行比较,返回对Dom树的更新操作。
以列表渲染为例,如果数据项的顺序被改变,为了尽量减少Dom的渲染操作,Vue将不是移动DOM元素来匹配数据项的改变(更不是全部销毁重新渲染),而是简单复用此处每个元素,并且确保它在特定索引下显示已被渲染过的每个元素,即就地复用策略。
比如:
在BC中间插入一个新的节点,如果不指定key,在页面Dom的真实变更如下:
原来ACBDE的Dom其实没有变化,只是在最后增加一个Dom,将原来显示C的Dom显示的内容改为显示F,依次类推。就是说,原来显示C对应的Dom假设在浏览器中的ID是C_dom,更新后显示C的Dom对应的ID就变成D_dom了。这样的方式其实不是更新的最优解。
有一个严重的问题就是,v-for如果不指定key,如果列表不只是展示功能,而是有勾选选中功能时,更新前后会导致选中项的错误变化。比如下面这个例子,一个可勾选的列表,可以动态的在列表顶端增加项。(代码在文末提供)
在选中第二项(JAVA)后,增加一项被选中的项变成了JS,依然是列表的第二项!
换一种方式,我们把修改第二项的样式,然后增加顶部增加一项
得到的结果依然和前面类似。也就是说,Dom一些属性没有跟随列表的更新过程中随着Dom树的更新跟着对应的内容更新,而是绑定在Dom上。当为v-for增加key属性后,这样的“bug”消失了。增加key属性后,列表项能绑定Dom节点,diff算法能跟踪每个节点的身份,从而重用和重新排序现有元素。
值得注意的是,如果key使用列表的index绑定,可能达不到想要的效果。因为index在列表改变的时候一起改变了,达不到作为列表项唯一标识的目的。
<div v-for="(item, index) in list" :key="index" >
为了给 Vue 一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,你需要为每项提供一个唯一 key attribute。建议尽可能在使用 v-for 时提供 key attribute,除非遍历输出的 DOM 内容非常简单,或者是刻意依赖默认行为以获取性能上的提
以上内容根据其他博客整理,经过自己的验证,增加自己的思考。如果有错误欢迎指正。
附录
<template>
<div id="app">
<label >Range:</label><input type="text" v-model="count"><br>
<label >Name:</label><input type="text" v-model="name">
<input type="button" value="添加" @click="add">
<div v-for="item in list" >
<input type="checkbox">[{{item.count}}]——{{item.name}}
</div>
</div>
</template>
<script>
export default {
name: 'ListRend',
data () {
return {
count: '',
name: '',
list: [
{count: 1001, name: 'JS'},
{count: 1002, name: 'JAVA'},
{count: 1003, name: 'C#'},
{count: 1004, name: 'C++'},
{count: 1005, name: 'C'}
]
}
},
methods: {
add () {
let p = {
count: this.count,
name: this.name
}
this.list.unshift(p)
}
}
}
</script>