Vue3 零基础每日笔记(016):v-for 列表渲染——key 的作用与为什么不能用 index

一、前言

上篇 v-if 管"显示谁",这篇 v-for 管"显示一堆":任务清单、商品列表、表格行,全靠它。本篇重头戏是那个经典面试题------key 是干嘛的?为什么官方反复强调别用 index 当 key? 很多写了一年 Vue 的人也答不上来,今天用实验讲明白。

二、v-for 基础:遍历数组

语法 v-for="(item, index) in 数组",:key 别忘了:

vue 复制代码
<template>
  <ul>
    <li v-for="(item, index) in fruits" :key="item.id">
      {{ index }}. {{ item.name }} ------ ¥{{ item.price }}
    </li>
  </ul>
</template>

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

const fruits = ref([
  { id: 1, name: '苹果', price: 5 },
  { id: 2, name: '香蕉', price: 3 },
  { id: 3, name: '西瓜', price: 12 }
])
</script>

只要模板结构来自数据循环,就必须写 :key(不写会有警告)。

三、遍历对象 / 数字 / 字符串

v-for 不只能遍历数组,四个场景的参数顺序注意记:

vue 复制代码
<template>
  <!-- 1. 对象:(值, 键, 索引) ------ 值在前! -->
  <li v-for="(value, key, index) in user" :key="key">
    {{ key }}: {{ value }}(第 {{ index }} 个属性)
  </li>

  <!-- 2. 数字:从 1 到 5,常用于分页页码 -->
  <span v-for="n in 5" :key="n">{{ n }} </span>

  <!-- 3. 字符串:逐字符遍历 -->
  <i v-for="(ch, i) in 'ABCD'" :key="i">{{ ch }}</i>

  <!-- 4. 嵌套:乘法口诀式结构 -->
  <div v-for="row in 3" :key="row">
    <span v-for="col in 3" :key="col">{{ row }}-{{ col }} </span>
  </div>
</template>

<script setup>
import { reactive } from 'vue'
const user = reactive({ name: '小明', age: 18, city: '北京' })
</script>

四、key 到底是干嘛的:虚拟 DOM 的"身份证"

你写的 template 会先变成虚拟 DOM(JS 对象描述的节点树),数据变化时生成新的虚拟 DOM,然后新旧对比(diff 算法),只把差异部分更新到真实 DOM。

问题来了:对比时,新列表的第 2 个节点,对应旧列表的第 2 个节点吗?key 就是节点间的对账凭证

  • 有 key:新旧列表按 key 匹配,谁对应谁一目了然,能复用的就地复用,只动真正变化的
  • 没 key / 用 index:Vue 按位置"对号入座",位置对上了就认为没变------一旦列表顺序变化,就会大量错位更新

五、实验:为什么不能用 index 当 key

经典翻车现场------一个带输入框的任务列表,在中间插入一项:

vue 复制代码
<template>
  <ul>
    <!-- 用 index 当 key 的版本 -->
    <li v-for="(task, index) in tasks" :key="index">
      {{ task }}
      <input placeholder="这里输入点内容" />
      <button @click="tasks.splice(index, 1)">删除</button>
    </li>
  </ul>
  <button @click="tasks.unshift('🔥 新任务')">在头部插入新任务</button>
</template>

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

const tasks = ref(['学 Vue', '写博客', '面试准备'])
</script>

复现步骤:

  1. 在第 1、2 个输入框里分别输入 aaabbb
  2. 点"在头部插入新任务"
  3. 见证翻车:输入框内容错位了------aaa 跑到了第二个框,bbb 跑到第三个框

原因:index 当 key 时,插入后每个位置的 index 都变了,但 key(0、1、2)还是 0、1、2。Vue 按 key 对比,认为"每个位置还是原来的节点,只是文本变了",于是就地更新文本,而输入框是非受控的(内部状态不跟着模板走),文本挪了、输入框没挪 → 错位。

:key="index" 换成 :key="task"(用唯一稳定值)再试:内容精确跟着自己的主人走,绝不错位。

结论记忆:key 必须是"这条数据的唯一稳定标识"(id),index 会随位置变化,等于没有身份证。

【截图位置:index 当 key 输入内容错位前后的对比图】

六、综合实战:任务清单(增删改过滤全家桶)

vue 复制代码
<template>
  <input v-model.trim="newTask" placeholder="输入任务" @keyup.enter="add" />
  <button @click="add">添加</button>

  <p>未完成 {{ undoneCount }} 项</p>

  <ul>
    <li v-for="task in tasks" :key="task.id">
      <input type="checkbox" v-model="task.done" />
      <span :class="{ done: task.done }">{{ task.text }}</span>
      <button @click="remove(task.id)">删</button>
    </li>
  </ul>

  <p v-if="tasks.length === 0">🎉 全部完成,无任务</p>
</template>

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

let id = 0
const newTask = ref('')
const tasks = ref([
  { id: ++id, text: '学完 v-for', done: true },
  { id: ++id, text: '做任务清单', done: false }
])

const undoneCount = computed(() => tasks.value.filter(t => !t.done).length)

function add() {
  if (!newTask.value) return
  tasks.value.unshift({ id: ++id, text: newTask.value, done: false })
  newTask.value = ''
}

function remove(id) {
  tasks.value = tasks.value.filter(t => t.id !== id)
}
</script>

<style scoped>
.done { text-decoration: line-through; color: #999; }
</style>

数据驱动一切:id 唯一做 key,增删只改数组,视图自动同步。

【截图位置:任务清单添加、勾选删除线、删除的运行效果】

七、踩坑记录

  1. v-for 和 v-if 同元素 :Vue3 中 v-if 先执行、拿不到 item 直接报错(Vue2 是 v-for 先执行,行为都变了)。过滤需求一律交给 computed:computed(() => list.value.filter(...))
  2. key 用 index:上面实验翻车了。有唯一 id 用 id;实在没有稳定 id 的静态展示列表才可勉强用 index
  3. key 用随机数 / Date.now():每次渲染 key 都不同,等于告诉 Vue"全是新节点",销毁重建性能灾难
  4. v-for 里改被遍历的数组:可以,Vue3 响应式数组在遍历中增删是安全的,但避免在回调里无脑 splice 造成无限循环
  5. 对象的遍历顺序:整数键会按数字升序排前面,其余按插入顺序------依赖遍历顺序做业务时要小心

八、今日小结

  • v-for 四连:数组 (item, index)、对象 (value, key)、数字、字符串
  • key 是虚拟 DOM diff 的对账凭证,必须是唯一稳定值
  • index 当 key 在"插入/删除 + 内部状态"场景必然错位
  • v-for 过滤数据用 computed,别和 v-if 挤在一起

下篇预告

列表有了,用户点按钮、敲键盘怎么响应?下一篇 017 事件处理进阶------$event 是什么、事件修饰符 .stop/.prevent/.once 怎么让代码干净一半。

相关推荐
TheBestRucy41 分钟前
PyTorch 基本使用学习笔记
pytorch·笔记·学习
ouynagda44 分钟前
51 单片机 LED 流水灯 + 数码管动态扫描学习笔记(STC89C52)
笔记·单片机·学习
toooooop81 小时前
thinkphp查询数据表最后的自增id
前端·javascript·数据库
智者知已应修善业1 小时前
【如何将此图变为000到101就返回】2026-4-7
驱动开发·经验分享·笔记·硬件架构·硬件工程
honkun61 小时前
vue 甘特图组件 vxe-gantt 配置拖拽调整任务起止日期时实时显示 Tooltip 提示
javascript·vue.js·甘特图·vxe-gantt
计算机毕设定制辅导-无忧学长1 小时前
《基于SpringBoot的家政服务管理平台的设计与实现》
java·vue.js·spring boot·计算机毕业设计选题推荐·家政服务管理平台
资讯综合2 小时前
2026全国云渲染网站排名 不同需求用户适配榜单
开发语言·前端·javascript
wixzjsh2 小时前
51单片机学习笔记|最小系统、中断、定时器+数码管动态扫描实现0~9999计数器
笔记·学习·51单片机
snow@li2 小时前
服务器运维: k3s 安装及配置的完整操作笔记
笔记