key 的特殊 attribute 主要用在 Vue 的虚拟 DOM 算法,在新旧 nodes 对比时辨识 VNodes。如果不使用 key,Vue 会使用一种最大限度减少动态元素并且尽可能的尝试就地修改/复用相同类型元素的算法。
而使用 key 时,它会基于 key 的变化重新排列元素顺序,并且会移除 key 不存在的元素。
有相同父元素的子元素必须有独特的 key。重复的 key 会造成渲染错误。
2. 举个🌰
可以运行一下代码,改变key为 索引idx(或者不设置key) 或者 item.id, 看下效果
- 当索引idx,作为key的时候(或者不设置key),选中一项,再点击添加新值的时候,选中的项会改变
- 当item.id作为key的时候,选中一项,再添加的时候,选中项不会变化
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
<div>
<input type="text" v-model="name">
<button @click="add">添加</button>
</div>
<ul>
<!-- <li v-for="(item,idx) in list" :key="idx"> -->
<li v-for="item in list" :key="item.id">
<input type="checkbox">ID:{{item.id}} -------name:{{item.name}}
</li>
</ul>
</div>
<script>
var vm = new Vue({
el: '#app',
data: {
name: '',
list: [{
id: 0,
name: '测试数据1'
},
{
id: 1,
name: '测试数据2'
},
{
id: 2,
name: '测试数据3'
}
]
},
methods: {
add() {
const newUser = {
id: this.list.length,
name: this.name
};
this.list.unshift(newUser);
}
}
})
</script>
</body>
</html>
可以看出,当key值为item.id的时候,checkbox选中项在添加新项的时候不会变化。为什么会这样呢?我们往下看
3. diff
在vue中,当数据发生变化,会触发更新,更新过程的核心就是新旧 vnode的diff。
diff的原理就是对前后的节点树同一层的节点进行对比,一层一层对比,如下
当某一层有很多相同的节点时,也就是列表节点时,diff算法的更新过程默认情况下也是遵循以上原则。
如下
我们希望可以在B和C之间加一个F,diff算法默认执行起来是这样的
即把C更新成F,D更新成C,E更新成D,最后再插入E,相当没有效率!
所以我们需要使用key来给每个节点做一个唯一标识,diff算法就可以正确的识别此节点,找到正确的位置区插入新的节点。如下图
在vue中,判断新旧节点的sameVnode(oldVnode, vnode)函数如下
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)))
)
}
可以看到,判断两个节点是否一样,首先判断了节点的key
因此,唯一的key值,会在diff比较新旧节点是否相同时,起到关键作用,起到更高效的更新虚拟节点的作用
4. 为什么不推荐索引index作为key
可以参考例子中的代码,在增删变化的场景中,元素的index可能是会变化的,diff算法时比较同级之间的不同,以key来进行关联,当对数组进行下标的变换时,比如删除第一条数据,那么以后所有的index都会发生改变,那么key自然也跟着全部发生改变,所以index作为key值是不稳定的,而这种不稳定性有可能导致性能的浪费,导致diff无法关联起上一次一样的数据。因此,能不使用index作为key就不使用index