在 Vue 中为什么不推荐用 index 做 key
唐超 2021/12/12
当 Vue.js 用 v-for 正在更新已渲染过的元素列表时,它默认用就地复用策略
# 用 index 作为 key 时,在对数据进行,逆序添加,逆序删除等破坏顺序的操作时,会全部更新。产生没必要的真实 DOM更新,从而导致效率低
# 用 index 作为 key 时,如果结构中包含输入类的 DOM,会产生错误的 DOM 更新
# 在开发中最好每条数据使用唯一标识固定的数据作为 key,比如后台返回的 ID,手机号,身份证号等唯一值
# 如果不存在对数据逆序添加,逆序删除等破坏顺序的操作时,仅用于渲染展示用时,使用 index 作为 key 也是可以的(但是还是不建议使用,养成良好开发习惯)
为什么我没有复现大部分时候我们即使使用index作为key,不会复现这个问题,这好像与我们上述分析不符。但是事实上,“使用index作为key并且不会出问题”这种场景,其实过程是这样的:
# 第一步:通过修改数据删除第一行,数据变化引起vue去更新视图,更新的过程中发现key相同,最终第一行保持不变,反而是第二行消失。这是第一次render。
# 第二步:第一行的VirtualDOM的确没有变,但是第一行的组件的props变了,由原来第一行的props,变成了第二行的props,由于props变化,第一行的组件需要使用新的props更新视图,最终第一行变成了第二行的样子。这是第二次render。
也就是说,“使用index作为key并且不会出问题”这种场景,是因为render了两次,才最终达成了视图的正确更新。
<template v-if="switched">
<p>正常的例子:删除哪行,的确是删了哪行</p>
<div v-for="(item, index) in data" :key="index">
<StateComponent :name="`依赖于状态的属性+${item.name}`"/>
<button @click="handleDelete(index)">删除这一行</button>
</div>
</template>
<template v-else>
<p>异常的例子:删除哪行,不符合预期</p>
<div v-for="(item, index) in data" :key="index">
<StaticComponent :name="`不依赖于状态的属性`"/>
<button @click="handleDelete(index)">删除这一行</button>
</div>
</template>
不更新示例,checkbox被复用 临时 DOM 状态 表单输入值
<input type="text" />
--------------------
<li v-for="(item, index) in list" :key="index">
<input type="checkbox" name="" id="" />
<span>item: {{ item }}</span>
<button @click="deleteItem(index)">删除</button>
</li>
依赖子组件状态
子组件有自己的data数据,则这个数据不更新
子组件
<div>
<span>{{ name }}</span>
count值为:{{ innerCount }}
<button @click="$emit('delete')">-</button>
</div>
...
data() {
return {
innerCount: this.count
}
}
innerCount这个数据不是props过来的,不是响应式
# 结论
在使用非文本节点的组件,且这个组件没有依赖于响应式的props,那么此时对于列表的删除操作会导致视图错乱。 如果是文本节点 Vue的Diff过程对文本节点有特殊处理,不管key一不一样,都会用“新的文本节点”覆盖“旧的文本节点”。
写列表渲染时, 依赖子组件状态
或临时 DOM 状态
的情况,如果有 删除、增加、排序这样的功能,不要把 index 作为 key。
# 笔记2
当我们修改了数据,数据的变动会触发订阅该数据变化的dom对象的更新,这个过程大致可分为三个阶段: