前言
这个东西算是一个毒瘤了。在 vue2
那会的编辑器都普遍建议在 v-for
里面写个唯一标识 key
,否则就会警告提示。
但唯一标识这种东西,后端可能不会给我们传 id
过来。又或者前端只是渲染一个静态列表,懒得自己挨个列表项都加个 id
属性。所以至少笔者本人常年有着使用索引值作为 key
属性的恶劣习惯。
直到今天在阅读官网文档的时候偶然想起了这个问题,便尝试了解了下。一了解还真的吓一跳汗流浃背了吧
代码案例
现在有两个组件 app.vue
和 TestComponent.vue
。app.vue
中使用 v-for 对包含 TestComponent.vue
和一个删除按钮的内容做了循环渲染。
点击删除按钮时会删除当前项。内容分别如下:
1 | <!-- app.vue --> |
解析
可以看到,在上面的例子中我们使用了 v-for
自带的 index
索引作为 key
的值。当我们尝试点击第二项的删除时,结果确是第三项被删除了。
这是因为在删除第二项后,原本第三项的 key
从 2
变成了 1
。这与删除之前的第二项的 key
是相同的。因此在 vue
眼里,第二行是一直存在的,不需要更新。依次类推,反而是最后一项消失了。
为什么平日遇不到这种异常
在平日开发中,我们均会向子组件传递相应的 props
,像上面案例这样是基本无意义的。我们可以略微更改一下案例,使之符合我们日常的开发场景
1 | <!-- app.vue --> |
在传递了 props
后,上面所说的情况依旧会发生。但在那之后又紧接着发生了连锁变化: 尽管第二行保持不变,但由于传递的 props
变为了下一行的内容。
因此第二行又在内容层面变为了第三行的内容,以此类推。最终看到的结果就像是第二行被删除了一样。
1 | // 删除第二行前 |
需要注意的是王五的随机数变了,这代表着王五这个组件被重新渲染了一下
v2 和 v3 有没有区别
答案是有的,我们可以对案例的内容再次修改一下(v2 是要使用 option
写法的,这里就不专门翻译了)
1 | <!-- TestComponent.vue --> |
我们将 props.name
使用的那行注释了,也就是传递过来的 props 并未被使用。此时我们再进行删除,就得到了截然不同的结果
- v2: 依旧是最后一项被删除,删除顺序混乱
- v3: 正常删除,第三行以及以后的随机数发生变化
这就说明了一点,在 v3 中,传入的 props
只要发生了变化,哪怕它未被使用,依旧会触发二次渲染。而 v2 只要不被使用则不会触发二次渲染。
结论
v-for
使用 index
作为 key
的值确实会出现问题,但其必须满足如下几个要求:
- 被循环的内容必须为父子组件关系
- 增删的操作必须在父组件中执行
- 子组件没有依赖响应式的
props
- 子组件并未使用响应式的
props
(仅限 vue2)