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 就有可能出现。


目录
相关文章
|
16天前
|
人工智能 JavaScript 算法
Vue 中 key 属性的深入解析:改变 key 导致组件销毁与重建
Vue 中 key 属性的深入解析:改变 key 导致组件销毁与重建
104 0
|
17天前
|
JavaScript UED
用组件懒加载优化Vue应用性能
用组件懒加载优化Vue应用性能
|
3月前
|
JavaScript
vue实现任务周期cron表达式选择组件
vue实现任务周期cron表达式选择组件
324 4
|
2月前
|
JavaScript 数据可视化 前端开发
基于 Vue 与 D3 的可拖拽拓扑图技术方案及应用案例解析
本文介绍了基于Vue和D3实现可拖拽拓扑图的技术方案与应用实例。通过Vue构建用户界面和交互逻辑,结合D3强大的数据可视化能力,实现了力导向布局、节点拖拽、交互事件等功能。文章详细讲解了数据模型设计、拖拽功能实现、组件封装及高级扩展(如节点类型定制、连接样式优化等),并提供了性能优化方案以应对大数据量场景。最终,展示了基础网络拓扑、实时更新拓扑等应用实例,为开发者提供了一套完整的实现思路和实践经验。
215 77
|
1月前
|
JavaScript 前端开发 开发者
Vue 自定义进度条组件封装及使用方法详解
这是一篇关于自定义进度条组件的使用指南和开发文档。文章详细介绍了如何在Vue项目中引入、注册并使用该组件,包括基础与高级示例。组件支持分段配置(如颜色、文本)、动画效果及超出进度提示等功能。同时提供了完整的代码实现,支持全局注册,并提出了优化建议,如主题支持、响应式设计等,帮助开发者更灵活地集成和定制进度条组件。资源链接已提供,适合前端开发者参考学习。
128 17
|
1月前
|
监控 JavaScript 前端开发
Vue 文件批量下载组件封装完整使用方法及优化方案解析
本文详细介绍了批量下载功能的技术实现与组件封装方案。主要包括两种实现方式:**前端打包方案(基于file-saver和jszip)** 和 **后端打包方案**。前者通过前端直接将文件打包为ZIP下载,适合小文件场景;后者由后端生成ZIP文件流返回,适用于大文件或大量文件下载。同时,提供了可复用的Vue组件`BatchDownload`,支持进度条、失败提示等功能。此外,还扩展了下载进度监控和断点续传等高级功能,并针对跨域、性能优化及用户体验改进提出了建议。可根据实际需求选择合适方案并快速集成到项目中。
143 17
|
3月前
|
缓存 JavaScript 前端开发
Vue 基础语法介绍
Vue 基础语法介绍
|
1月前
|
JavaScript 前端开发 UED
Vue 手风琴实现的三种常用方式及长尾关键词解析
手风琴效果是Vue开发中常见的交互组件,可节省页面空间、提升用户体验。本文介绍三种实现方式:1) 原生Vue结合数据绑定与CSS动画;2) 使用Element UI等组件库快速构建;3) 自定义指令操作DOM实现独特效果。每种方式适用于不同场景,可根据项目需求选择。示例包括产品特性页、后台菜单及FAQ展示,灵活满足多样需求。附代码示例与资源链接,助你高效实现手风琴功能。
101 10
|
28天前
|
JavaScript 前端开发 UED
Vue 表情包输入组件实现代码及详细开发流程解析
这是一篇关于 Vue 表情包输入组件的使用方法与封装指南的文章。通过安装依赖、全局注册和局部使用,可以快速集成表情包功能到 Vue 项目中。文章还详细介绍了组件的封装实现、高级配置(如自定义表情列表、主题定制、动画效果和懒加载)以及完整集成示例。开发者可根据需求扩展功能,例如 GIF 搜索或自定义表情上传,提升用户体验。资源链接提供进一步学习材料。
71 1
|
1月前
|
JavaScript 前端开发 UED
Vue 表情包输入组件的实现代码:支持自定义表情库、快捷键发送和输入框联动的聊天表情解决方案
本文详细介绍了在 Vue 项目中实现一个功能完善、交互友好的表情包输入组件的方法,并提供了具体的应用实例。组件设计包含表情分类展示、响应式布局、与输入框的交互及样式定制等功能。通过核心技术实现,如将表情插入输入框光标位置和点击外部关闭选择器,确保用户体验流畅。同时探讨了性能优化策略,如懒加载和虚拟滚动,以及扩展性方案,如自定义主题和国际化支持。最终,展示了如何在聊天界面中集成该组件,为用户提供丰富的表情输入体验。
92 8
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等