vue v-for列表渲染, 无key、key为index 、 有唯一key三种情况下的对比。 列表有删除操作时的表现

在 Vue3 的 v-for 列表渲染中,key 的使用方式直接影响列表更新时的 DOM 行为,尤其是包含删除操作时,不同 key 策略会呈现不同的表现(甚至异常)。下面从「无 key」「key 为 index」「key 为唯一值」三种场景逐一分析,并结合删除操作的示例说明差异。

核心原理铺垫

Vue 的虚拟 DOM 对比(diff 算法)依赖 key 来识别节点的唯一性:

  • 有唯一 key:Vue 能精准判断节点的增 / 删 / 移,只更新变化的 DOM;
  • 无 key/key 为 index:Vue 无法识别节点唯一性,会通过「就地复用」策略更新 DOM,可能导致 DOM 与数据不匹配。

场景复现准备

先定义基础组件,包含一个列表和删除按钮,后续仅修改 v-forkey

vue 复制代码
<template>
  <div>
    <div v-for="(item, index) in list" :key="xxx"> <!-- 重点:xxx 替换为不同值 -->
      <input type="text" v-model="item.name">
      <button @click="deleteItem(index)">删除</button>
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue'

// 初始化列表(每个项有唯一 id,模拟业务场景)
const list = ref([
  { id: 1, name: '张三' },
  { id: 2, name: '李四' },
  { id: 3, name: '王五' }
])

// 删除方法
const deleteItem = (index) => {
  list.value.splice(index, 1)
}
</script>

场景 1:无 key(不写 :key)

表现

删除某一项后,输入框的内容会「错位」,DOM 看似更新但数据与视图不匹配。

示例过程

  1. 初始状态:输入框分别输入「张三」「李四」「王五」;
  2. 删除索引 1(李四);
  3. 结果:列表只剩两项,但输入框显示「张三」「王五」→ 看似正常?✘ 实际异常点:若列表项包含「状态绑定 / 组件实例」(比如输入框焦点、自定义组件内部状态),会出现错位。(补充:无 key 时 Vue 会按「节点位置」复用 DOM,删除索引 1 后,原索引 2 的 DOM 会被移到索引 1 位置,仅更新文本内容,但组件 / 输入框的内部状态会保留。)

本质

Vue 认为「节点位置」即唯一标识,直接复用 DOM 节点,仅更新节点的文本 / 属性,忽略数据的唯一性,若列表项有「非响应式状态」(如输入框焦点、组件内部变量),会导致状态错位。

场景 2:key 为 index(:key="index")

表现

删除操作后,输入框内容错位更明显(比无 key 更易复现),是日常开发中最易踩的坑。

示例过程

  1. 初始状态:输入框分别输入「张三」「李四」「王五」;

  2. 删除索引 1(李四);

  3. 结果:

    • 数据层面:list 变为 [{id:1,name:'张三'}, {id:3,name:'王五'}]
    • 视图层面:输入框显示「张三」「李四」(而非「王五」),DOM 与数据完全错位。

原因分析(关键)

操作前 操作后(删除索引 1)
索引 0 → key0 → 张三 索引 0 → key0 → 张三(复用原 DOM,无变化)
索引 1 → key1 → 李四 索引 1 → key1 → 王五(复用原索引 1 的 DOM,仅更新文本,但输入框的 v-model 绑定的是 item.name,为何错位?)
索引 2 → key2 → 王五 索引 2 被删除

核心错位逻辑:当 key 为 index 时,删除索引 1 后,原索引 2 的项(id:3,name: 王五)会「占据」索引 1 的位置。Vue 的 diff 算法认为:

  • key0(索引 0)的节点不变,复用;
  • key1(索引 1)的节点需要更新,于是将原索引 1 的 DOM 节点的 item 替换为新的索引 1 项(王五),但输入框的 DOM 节点是复用的,v-model 的绑定是「事后更新」,导致视觉上输入框内容未同步(或出现延迟 / 错位)。

极端案例(含组件状态)

若列表项是自定义组件(有内部状态):

vue 复制代码
<!-- 自定义组件 -->
<template>
  <div>{{ item.name }} - 内部状态:{{ innerState }}</div>
</template>
<script setup>
const props = defineProps(['item'])
const innerState = ref(Math.random()) // 组件内部状态
</script>

<!-- 列表使用 -->
<div v-for="(item, index) in list" :key="index">
  <MyComponent :item="item" />
  <button @click="deleteItem(index)">删除</button>
</div>

删除索引 1 后,原索引 2 的组件会复用原索引 1 的组件 DOM,内部状态(innerState)不会重置,导致「王五」显示的是「李四」组件的内部状态,完全错位。

场景 3:key 为唯一值(:key="item.id")

表现

删除操作后,DOM 精准更新,无任何错位,输入框 / 组件状态与数据完全匹配。

示例过程

  1. 初始状态:输入框输入「张三」「李四」「王五」;

  2. 删除索引 1(李四,id:2);

  3. 结果:

    • 数据层面:list 变为 [{id:1,name:'张三'}, {id:3,name:'王五'}]
    • 视图层面:直接移除 id:2 对应的 DOM 节点,剩余节点的 DOM 完全保留(输入框内容、组件状态均无错位)。

原因分析

Vue 通过唯一 key(item.id)识别节点:

  • 删除 id:2 的项时,Vue 直接找到 key=2 的 DOM 节点并移除;
  • 剩余项的 key(1、3)与原节点一致,复用 DOM 且状态不变;
  • 无任何 DOM 复用错位,数据与视图完全同步。

本质

唯一 key 让 Vue 能精准匹配「数据项」和「DOM 节点」,diff 算法会:

  1. 对比新旧列表的 key 集合;
  2. 移除不存在的 key(如 2);
  3. 保留存在的 key(1、3),仅更新内容(若有变化);
  4. 新增的 key(若有)则创建新 DOM 节点。

三种场景对比表

场景 删除操作后的表现 底层逻辑 适用场景
无 key 文本看似正常,组件 / 输入框状态可能错位 按位置复用 DOM,无唯一性识别 仅纯文本列表,无状态 / 输入框
key 为 index 输入框 / 组件状态明显错位,数据与视图不匹配 按索引复用 DOM,索引变化导致错位 临时静态列表(无增删改)
key 为唯一值 无错位,DOM 精准更新 按唯一标识匹配节点,精准增删 所有有增删改的列表(推荐)

关键总结

  1. 禁止在有增删改的列表中使用 index 作为 key:这是 Vue 官方明确不推荐的做法,会导致 DOM 复用错位;
  2. 无 key 等同于 key 为 index:Vue 内部会默认使用 index 作为隐式 key,表现一致;
  3. 唯一 key 必须是数据本身的属性 :不能是临时生成的唯一值(如 Math.random()),否则每次渲染都会认为是新节点,导致 DOM 全量重建,性能极差;
  4. 唯一 key 的选择:优先使用业务唯一标识(如 id、手机号、订单号),避免使用 index / 随机值。

扩展:Vue3 对 key 的优化

Vue3 的 diff 算法(PatchFlags)相比 Vue2 更高效,但key 的核心作用不变------ 唯一 key 仍是保证列表更新准确性的关键,Vue3 仅优化了「有 key 时的对比效率」,并未改变「无 key/index key 导致的错位问题」。

最终正确示例

vue 复制代码
<template>
  <div>
    <div v-for="(item, index) in list" :key="item.id">
      <input type="text" v-model="item.name">
      <button @click="deleteItem(index)">删除</button>
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const list = ref([
  { id: 1, name: '张三' },
  { id: 2, name: '李四' },
  { id: 3, name: '王五' }
])

const deleteItem = (index) => {
  list.value.splice(index, 1)
}
</script>
相关推荐
狗哥哥2 小时前
Vue 3 统一面包屑导航系统:从配置地狱到单一数据源
前端·vue.js·架构
鱼鱼块3 小时前
从后端拼模板到 Vue 响应式:前端界面的三次进化
前端·vue.js·面试
谎言西西里3 小时前
从模板渲染到响应式驱动:前端崛起的技术演进之路
vue.js
一 乐3 小时前
家政管理|基于SprinBoot+vue的家政服务管理平台(源码+数据库+文档)
前端·javascript·数据库·vue.js·spring boot
源码获取_wx:Fegn08954 小时前
基于springboot + vue停车场管理系统
java·vue.js·spring boot·后端·spring·课程设计
cc蒲公英4 小时前
vue 对象、数组增删改,对比vue2和vue3 —— 最新总结2025
前端·javascript·vue.js
_一两风4 小时前
揭秘 ChatGPT 同款“打字机”特效:前端流式输出 (Streaming) 原理全解
前端·vue.js·openai
三翼鸟数字化技术团队5 小时前
vue3组件二次封装-另外一种思路
vue.js
老华带你飞5 小时前
宠物商城销售|基于Java+ vue宠物商城销售管理系统(源码+数据库+文档)
java·开发语言·数据库·vue.js·spring boot·宠物