← 返回题目列表

v-for 中 key 的作用是什么?为什么不推荐用 index?

高频 中等 第 5 / 27 题 更新于 2026/07/27
Vuev-forkeyDiff

简化版

key 用来标识列表节点身份,帮助 Vue 在 Diff 时复用和移动正确的 DOM 或组件实例。不推荐用 index 作为 key,因为插入、删除、排序后 index 会变化,可能导致状态错乱和错误复用。

详细版

推荐:

<li v-for="item in list" :key="item.id">
  {{ item.name }}
</li>

不推荐:

<li v-for="(item, index) in list" :key="index">

如果列表只是静态展示、不排序不增删,用 index 问题不大。但动态列表、表单项、组件列表中,必须用稳定唯一 id。

key 的作用不是让渲染一定更快,而是让节点身份更准确,减少错误复用。

完整版教学

一、Diff 需要判断谁是谁

列表更新时,Vue 要比较新旧 vnode,决定复用、移动、创建还是删除。key 就像每个节点的身份证。

没有稳定 key,框架只能按位置猜测,可能把旧节点复用到错误数据上。

二、index 作为 key 的问题

假设列表 [A, B, C] 用 index 作为 key,删除 A 后变成 [B, C]。B 的 index 从 1 变成 0,C 从 2 变成 1。

框架看到 key 0 还在,可能把原来 A 的 DOM/组件实例复用给 B。这在输入框列表中会出现输入内容错位。

三、什么时候 index 可以接受

如果列表完全静态,不会插入、删除、排序,也没有子组件内部状态,用 index 风险较低。

但面试和工程最佳实践都建议用业务唯一 id,这样未来需求变化也更安全。

是否可用 index 要同时满足“顺序固定、数量固定、节点无需要跟随业务项的状态”。这三个条件任意一个可能变化,就应在数据进入视图前准备稳定 id,而不是等输入错位后再补 key。

四、key 和性能的关系

key 不只是性能优化标记。错误 key 会导致错误复用;正确 key 能让 Diff 更准确地移动节点。

有时加 key 反而会让节点重建,比如你故意改变组件 key 来重置组件状态。这也是 key 控制身份的体现。

五、面试追问与工程落地

key 常见追问是“为什么有时故意改变 key”。因为 key 决定组件身份。给表单、弹窗、详情组件换 key,可以强制 Vue 销毁旧实例并创建新实例,从而重置内部状态。这是 key 的主动使用场景。

还会问“不写 key 会怎样”。Vue 会尽量按就地更新策略复用节点,简单静态列表可能没问题,但有状态子组件、输入框、动画列表、排序增删时容易出现错位。稳定 key 是动态列表的基本要求。

工程里 key 要唯一且稳定。不要用随机数当 key,因为每次渲染都变,等于告诉 Vue 所有节点都不是原来的,会导致频繁销毁重建,性能和状态都会受影响。

六、从 keyed Diff 的匹配与移动理解身份

keyed Diff 会先复用新旧两端可直接匹配的节点,再为中间未知区域建立 key 到新位置的映射,判断哪些旧节点可复用、哪些应删除。为了减少真实 DOM 移动,Vue 还会根据新旧位置关系寻找可保留的最长递增子序列;所以 key 既参与正确性,也影响复用和移动决策。

旧:A(key=1)  B(key=2)  C(key=3)  D(key=4)
新:B(key=2)  D(key=4)  A(key=1)  C(key=3)

key 映射先确认四个身份仍存在,再决定最少移动哪些节点。
index key 只会得到 0、1、2、3,看不出业务项已经换位。
key 选择插入或排序后的身份典型结果
稳定业务 id同一数据保持同一 key组件实例和 DOM 正确复用、移动
数组 indexkey 跟位置而非数据走输入值、局部状态可能串项
每次新随机数所有 key 都变化全部销毁重建,状态丢失
重复 key身份不唯一匹配结果不可可靠推断并产生警告

[A,B,C] 删除头部 A 为例,业务 id 能明确删除 1 个节点并复用 B、C 两个实例;index key 则把旧 key 0、1 继续对应到新位置,可能把 A、B 的局部 DOM 状态分别交给 B、C。无状态纯文本看起来可能没问题,有输入框或子组件状态时就会暴露错位。

key 也可以主动改变身份。详情页从用户 1 切到用户 2 时,如果业务要求完整重置子组件本地状态,把用户 id 作为组件 key 会触发旧实例卸载、新实例挂载;这是一种明确的生命周期控制,同时要接受重建成本。

<EditorRow
  v-for="item in items"
  :key="item.id"
  :item="item"
/>

这里 item.id 应在同一组兄弟节点中唯一,并在该实体的生命周期内稳定。不要把整个对象直接作为 key:对象 key 依赖引用身份,重新从接口组装出内容相同的新对象时引用已经变化;业务主键更能表达“仍是同一条数据”。

无 key 的就地更新并不等于每次都错。纯文本、无内部状态且顺序固定时,按位置复用可能足够;一旦列表能插入、删除、排序,或行内含输入框、动画和有状态子组件,就必须优先保证身份正确。

key 回答的是“新旧两棵树中的这两个节点是不是同一个实体”,不是简单的“有没有重复数据”或“能不能让渲染变快”。

七、常见误区与追问

  • 误区:key 只是为了提高列表性能。 它首先定义节点身份,错误 key 会导致状态错位,性能只是复用和移动策略的结果。
  • 误区:index 在任何 v-for 中都绝对不能用。 完全静态、无状态且永不重排的列表风险较低,但业务一旦增删排序就失去稳定身份。
  • 误区:用 Math.random 生成 key 最唯一也最安全。 每次渲染都变等于身份全部失效,会造成组件反复销毁和创建。
  • 追问:为什么输入框列表最容易暴露 index key 问题? DOM 值、焦点和组件局部状态会随错误实例复用留在位置上,而业务数据已经换项。
  • 追问:key 必须全局唯一吗? 只需在同一组兄弟节点中唯一,并在该业务实体生命周期内保持稳定。
  • 追问:为什么改变组件 key 能重置状态? Vue 将其视为不同 vnode 身份,旧组件卸载,新组件重新创建。
  • 追问:keyed Diff 为什么还要计算递增子序列? 已匹配节点中保持相对递增的部分可留在原位,从而减少真实 DOM 移动次数。

八、加强记忆

key 是列表节点的身份证,index 是排队号码。队伍一变,排队号码就变了;身份证不会变。所以动态列表用稳定唯一 id。

面试作答要把“稳定身份 → 正确复用 → 必要时最少移动”这条因果链说完整,并明确随机 key 会主动切断复用。