Vue - v-for 中为什么不能用 index 作为 key

简介: Vue - v-for 中为什么不能用 index 作为 key

这是一篇脱坑日记,在做项目的过程中,我使用了 v-for 渲染子组件时,并将 index 绑定给了 key,这一行为导致删除操作会误删子组件,实际上删除的组件并不是你预期的那个。而且我在排查错误的过程中打印 log 的数据信息均正常,唯独在执行删除操作时出现异常。


深陷其坑而不知具体原因,询问大佬后恍然大悟,究其根源还是自己太年轻,只知道使用 v-for 的时候需要绑定 key,却不知道在复杂情况下不能用 index 作为 key,特此总结这篇博客。

1、先来看一个例子

假设你有三个子组件,每个子组件里面有一个「有状态的」孙子组件。现在用户点击删除按钮,把第二个子组件删掉了,请问结果是怎样的?你可能会说,那还用问?当然是 2 消失了,因为 data 里的数组从 [1,2,3] 变成了 [1,3]。实际上你没有考虑全面:注意看图中的绿色正方形没有被删除。

原因很简单,你认为你删除了 2,但 Vue 会认为你做了两件事:(1)把 2 变成了 3、(2)然后把 3 删除。


Vue 为什么要舍近求远呢?看看这两个数组:[1,2,3] 和 [1,3]。人类会说,这不就是少了个 2 吗?但是计算机会怎么对比数组?遍历!首先对比 1 和 1,发现 [ 1 没变 ];然后对比 2 和 3,发现 [ 2 变成了 3 ];最后对比 undefined 和 3,发现 [ 3 被删除了」。所以计算机的结论是:[ 2 变成了 3 ] 以及 [ 3 被删除了 ]。


既然 [1 没变】,那么就地复用之前的 1 和三角形就好了。

既然 [ 2 变成了 3 ],那么正方形左边的 2,当然要改成 3。里面的正方形就地复用(正方形没有被删除)。因为正方形是孙子元素的 data,不受 [ 2 变成 3 ] 的影响,所以可以就地复用。

既然 [ 3 被删除了」,之前的 [圆形] 当然应该被删掉,里面的子元素也要删除。

2、上述问题的解决方法

怎么解决这个问题呢?怎么让 Vue 知道我删除的是第二个,不是第三个?用 id 作为 key 就行了,不信你再看:

我们以计算机的角度来思考一下:原本的数组是 [{id:1,value:1},{id:2,value:2].{id:3,value:3],点击删除之后的数组是 [{id:1,value:1},{id:3,value:3}],Vue 会在删除操作后执行 DOM diff 算法,对比前后两次 dom 节点的异同:


首先发现 id 从 1 2 3 变成了 1 3,说明第二项被删除了

然后依次对比 id:1 的项和 id:3 的项,发现删除前后这两个节点没变化。

所以计算机得出结论:第二项被删除了。符合人类预期!代码示例

3、高效的 Diff 算法原理

其实不只是 Vue,React 中在执行列表渲染时也会要求给每个组件添加上 key 这个属性。要解释 key 的作用,不得不先介绍一下虚拟 DOM 的 Diff 算法了。Vue 和 React 都实现了一套虚拟 DOM,使我们可以不直接操作 DOM 元素,只操作数据便可以重新渲染页面。而隐藏在背后的原理便是其高效的 Diff 算法。


Vue 和 React 的虚拟 DOM 的 Diff 算法大致相同,其核心是基于两个简单的假设:


1. 两个相同的组件产生类似的DOM结构,不同的组件产生不同的DOM结构。

2. 同一层级的一组节点,他们可以通过唯一的id进行区分。

基于以上这两点假设,使得虚拟 DOM 的 Diff 算法的复杂度从 O(n3) 降到了 O(n)。看图分析:

当页面的数据发生变化时,Diff 算法只会比较同一层级的节点:


如果节点类型不同,直接干掉前面的节点,再创建并插入新的节点,不会再比较这个节点以后的子节点了。

如果节点类型相同,则会重新设置该节点的属性,从而实现节点的更新。

当某一层有很多相同的节点时,也就是列表节点时,Diff 算法的更新过程默认情况下也是遵循以上原则。比如下面这种情况,我们希望可以在 B 和 C 之间加一个 F,Diff 算法默认执行起来是这样的:

即把 C 更新成 F,D 更新成 C,E 更新成 D,最后再插入 E,是不是很没有效率?

所以我们需要使用key来给每个节点做一个唯一标识,Diff算法就可以正确的识别此节点,找到正确的位置区插入新的节点。

所以一句话,key 的作用主要是为了高效的更新虚拟 DOM。另外 Vue 中在使用 相同标签名元素的过渡切换 时,也会使用到 key 属性,其目的也是为了让 Vue 可以区分它们,否则 vue 只会替换其内部属性而不会触发过渡效果。

4、不能用 index 作为 key

为什么不能用 index 作为 key,如果你用 index 作为 key,那么在删除第二项的时候,index 就会从 1 2 3 变成 1 2(而不是 1 3),那么 Vue 依然会认为你删除的是第三项。也就是会遇到上面一样的 bug。


所以,永远不要用 index 作为 key。永远不要!除非你是大神。能清楚地知道如何解决 index做 key 带来的 bug。有人说简单的场景可以用 key。问题在于,你如何确保需求会一直保持简单?只要出现了删除一项或新增一项的需求,而且这一项里面含有子组件,上面说的 bug 就有可能出现。


目录
相关文章
|
2天前
|
JavaScript 前端开发 网络架构
vue 路由器history和hash工作模式
vue 路由器history和hash工作模式
|
2天前
|
缓存 监控 JavaScript
如何优化 Vue 的执行流程?
【10月更文挑战第2天】
91 59
|
2天前
|
JavaScript
vue知识点
vue知识点
16 5
|
1天前
|
JavaScript 前端开发 Java
【Vue】大悟Vue的核心之MVVM
【Vue】大悟Vue的核心之MVVM
8 1
|
1天前
|
JavaScript 前端开发 安全
如何快速上手VUE框架
如何快速上手VUE框架
6 0
|
1天前
|
JavaScript 索引
vue 表格数据上下移动并增加背景色
vue 表格数据上下移动并增加背景色
7 0
|
4天前
|
JSON 缓存 JavaScript
vue尚品汇商城项目-day01【1.vue-cli脚手架初始化项目生成文件的介绍】
vue尚品汇商城项目-day01【1.vue-cli脚手架初始化项目生成文件的介绍】
13 0
|
5天前
|
JavaScript 数据格式
vue3 + Ant design vue formItem 无法使用嵌套的form表单校验
vue3 + Ant design vue formItem 无法使用嵌套的form表单校验
27 1
|
4天前
|
JSON JavaScript 前端开发
vue尚品汇商城项目-day00【项目介绍:此项目是基于vue2的前台电商项目和后台管理系统】
vue尚品汇商城项目-day00【项目介绍:此项目是基于vue2的前台电商项目和后台管理系统】
14 1
|
4天前
|
JavaScript 前端开发
vue尚品汇商城项目-day01【4.完成非路由组件Header与Footer业务】
vue尚品汇商城项目-day01【4.完成非路由组件Header与Footer业务】
16 2