Vue 的 key 到底有什么用?
结论
key 属性的主要作用是对元素加上了一个唯一标识,主要作用在 Vue 的 diff 算法,在新旧 nodes 对比时辨识 VNodes。在 v-for 循环出来的元素发生变化时,diff 算法中会使用 key + 元素标签作为 diff 条件,找到需要更改的真实 DOM 部分,从而进行局部更新,而不是 DOM 全量更新。所以 key 属性的独特性对于性能优化和避免潜在的渲染错误至关重要。
v-for 的 key 可以不设置 key 吗?
先说结论:对于静态列表可以,对于动态列表可能会导致渲染错误。
下面来看一个案例:
<template>
<div>
<ul>
<li v-for="(item, index) in listData">
<input type="text">
<button @click="deleteProperty(index)">删除</button>
</li>
</ul>
</div>
</template>
<script>
export default {
name: 'key',
data() {
return {
listData: [
{
id: 11,
value: 1
},
{
id: 22,
value: 2
},
{
id: 33,
value: 3
},
{
id: 44,
value: 4
},
{
id: 55,
value: 5
}
]
}
},
methods: {
// 删除元素操作
deleteProperty(index) {
this.listData.splice(index, 1);
}
}
}
</script>
代码执行完毕后,我们可以看到效果:
此时我们点击上图所示的删除按钮,然后查看结果:
会发现,我们删除的是 3 。但是看效果,实际是 5 被删除了,但是 3 还是存在的。这是为啥呢?
原因:
在 vue 里的 diff 算法中有一个判断节点是否相同的逻辑:
下面解释一下相关变量与方法:
a:VNode1
b:VNode2
key:节点所绑定的key
sel:节点标签名
这里讲一个注意点:当节点没有 key 的时候,这时的 key 默认是 undefined,如果前后变更的节点都没有 key 存在且元素标签名相同时,就判断这两个节点是相同的。
然后我们接着往下看 diff 过程:
旧 为更新前的 VNodes,新 为更新后的 VNodes,因为我们删除了第 3 个节点,所以 新 就只剩下 4 个节点了。
首先新旧 1、2 节点是没有变化的,所以保持原样。
我们来看旧节点的 3 节点与新节点的 4 节点对比,首先根据上面 diff 算法中判断节点是否相同的逻辑来看,这两者的共同点为:
1、sel 为 li 2、key 为 undefined 所以这两个节点满足节点相同的条件。
此时我们就要进行对比这个元素里的子节点,子节点的相同点为: 1、sel 为 input 2、key 为 undefined 此时这两个子节点也满足节点相同的条件。
那么也就是说这两个节点都是完全一样的,在 diff 算法中的处理就是 DOM 元素复用,所以新 4 元素将不会进行任何修改,直接复用旧 3 元素。
然后旧节点的 4 节点与新节点的 5 节点对比,情况也是同上。新 5 元素将不会进行任何修改,直接复用旧 4 元素。
最后还剩下一个旧 5 元素,发现没有可对比的新元素了,所以这里直接进行了删除。(这就是为什么原本删除了 3 元素,但是最后面却发现删除了 5 元素)。
然后我们使用 key 后,再尝试一下:
<li
v-for="(item, index) in listData"
:key="item.id"
>
<input type="text">
<button @click="deleteProperty(index)">删除</button>
</li>
发现正常了,这里的 diff 过程如下:
使用了 key 绑定后,新旧节点都能各自匹配上。匹配完毕后发现 3 是多余的,故能正常删除 3 元素。
2)v-for 的 key 可以设置索引吗?
先说结论:对于静态列表完全可以,对于有增删的场景的列表,同样会导致 diff 错误。
还是使用上面的案例,key 的值改成 index:
<li
v-for="(item, index) in listData"
:key="index"
>
<input type="text">
<button @click="deleteProperty(index)">删除</button>
</li>
还是,执行删除第 3 个元素,这里贴一个新旧节点的 key 对比表格
| 旧节点的 key | 新节点的 key |
|---|---|
| 0 | 0 |
| 1 | 1 |
| 2 | 2 |
| 3 | 3 |
| 4 | - |
因为 key 是每次动态生成的,不具有唯一性,所以当元素数量变小的时候,对应的元素的 key 值可能会改变。所以这里也会导致更新错误的问题