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

目录

1、先来看一个例子

2、上述问题的解决方法

3、高效的 Diff 算法原理

4、不能用 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 就有可能出现。

  • 6
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值