Vue3 v-for:常用用法与开发高频坑点

前言:

v-for 是 Vue3 中最高频的列表渲染指令,要写 Vue 页面,几乎天天都要和 v-for 打交道:表格、下拉选项、卡片列表、动态表单...... 全靠它循环渲染。很多人觉得 v-for 很简单,但是开发中经常遇到:列表数据错乱、视图不更新、控制台报错、输入框内容串值等玄学问题。 这些问题大多不是 Vue bug,而是 v-for 使用细节没注意。

一、v-for 常用用法:

1. 遍历数组(最常用)

语法:v-for="(item, index) in array"

  • item:数组当前项
  • index:当前下标索引
xml 复制代码
<template>
  <ul>
    <li v-for="(item, index) in userList" :key="item.id">
      {{ index + 1 }}. {{ item.name }} - {{ item.age }}
    </li>
  </ul>
</template>

<script setup>
import { ref } from 'vue'
const userList = ref([
  { id: 1, name: '张三', age: 22 },
  { id: 2, name: '李四', age: 25 },
  { id: 3, name: '王五', age: 21 }
])
</script>

2. 遍历对象

语法:v-for="(value, key, index) in object"

顺序记住:值、键名、索引

xml 复制代码
<template>
  <div v-for="(val, key, idx) in info" :key="key">
    {{ idx }} | {{ key }}:{{ val }}
  </div>
</template>

<script setup>
import { reactive } from 'vue'
const info = reactive({
  name: '小明',
  gender: '男',
  address: '上海'
})
</script>

3. 遍历数字,循环固定次数

可以直接传入数字,从 1 开始循环:

xml 复制代码
<!-- 输出:1 2 3 4 5 -->
<span v-for="n in 5" :key="n">{{ n }}</span>

适合做分页页码、固定数量占位骨架屏。

4. template 循环,不生成多余 DOM

<template> 是占位标签,渲染后不会生成真实 DOM,想要循环多个标签又不想外层包一层 div,就用它:

xml 复制代码
<template>
  <template v-for="item in goodsList" :key="item.id">
    <p class="title">{{ item.title }}</p>
    <p class="price">¥{{ item.price }}</p>
  </template>
</template>

5. 循环自定义组件

循环组件和普通标签写法一致,key 同样不能少,props 正常传递:

ini 复制代码
<GoodsCard
  v-for="item in goodsList"
  :key="item.id"
  :goods="item"
/>

6. 列表过滤:优先使用 computed

不要直接在 v-for 里写过滤逻辑,模板里尽量简洁,复杂过滤交给计算属性,缓存性能更好:

xml 复制代码
<template>
  <li v-for="item in hotList" :key="item.id">{{ item.title }}</li>
</template>

<script setup>
import { ref, computed } from 'vue'
const goodsList = ref([
  { id: 1, title: '商品A', hot: true },
  { id: 2, title: '商品B', hot: false },
  { id: 3, title: '商品C', hot: true }
])
// 只展示热门商品
const hotList = computed(() => {
  return goodsList.value.filter(item => item.hot)
})
</script>

二、开发高频坑点 + 解决方案:

坑点 1:v-for 和 v-if 写在同一个标签上(头号大坑),Vue3 官方文档明确不建议 v-for 和 v-if 共存于同一个元素。

❌ 错误写法:

xml 复制代码
<!-- 不推荐!v-for 优先级高于 v-if,会先循环全部再判断,性能差,逻辑容易出问题 -->
<li v-for="item in list" v-if="item.isShow" :key="item.id">
  {{ item.name }}
</li>

两种推荐方案:

1. template 包裹,v-for 放外层 template

ini 复制代码
<template v-for="item in list" :key="item.id">
  <li v-if="item.isShow">{{ item.name }}</li>
</template>

2. 优先 computed 提前过滤数组(性能最优)

坑点 2:key 绑定 index,列表增删、拖拽后数据 DOM 错位

❌ 错误写法:

css 复制代码
<li v-for="(item, index) in list" :key="index">{{ item.name }}</li>

问题点: 当数组中间删除、插入、拖拽排序时,index 会重新变化,Vue Diff 算法会复用错误 DOM,输入框内容串掉、数据和界面对不上,这个坑在带表单的列表里非常致命。

推荐写法:

使用业务唯一 ID

css 复制代码
<li v-for="item in list" :key="item.id">{{ item.name }}</li>

只有一种特殊情况可以用 index:列表是静态写死的,永远不会新增、删除、排序。业务动态列表一律不要用 index 做 key。

坑点 3:直接通过数组下标赋值,视图不更新

❌ 容易失效写法

css 复制代码
// 直接下标赋值,Vue3 Proxy 虽然比Vue2好,但复杂场景依然会出现不更新
list.value[0] = { id: 99, name: '测试' }

解决办法:

  1. 使用 splice 修改
php 复制代码
list.value.splice(0, 1, { id: 99, name: '测试' })
  1. 替换数组引用
ini 复制代码
list.value = [...list.value]

坑点 4:key 值重复,控制台警告,渲染错乱

同一个循环内 key 重复,Vue 抛出警告,DOM 随机错乱,很难复现排查。

xml 复制代码
<!-- 两条数据id相同,key重复 -->
<li v-for="item in list" :key="item.id">{{ item.name }}</li>

原因:后端返回数据 id 重复、模拟数据写重了。

解决:保证 key 在当前循环内唯一;没有唯一 id 时前端生成 uuid。

坑点 5:循环组件收集 ref,直接 ref 拿不到组件实例

❌ 错误写法

ini 复制代码
<MyInput v-for="item in list" :key="item.id" ref="inputRef" />

这样写 inputRef 只会拿到最后一个组件实例。

推荐写法:

Vue3 使用函数式 ref 收集

xml 复制代码
<MyInput v-for="item in list" :key="item.id" :ref="setRef" />

<script setup>
const compList = []
const setRef = (el) => {
  if (el) compList.push(el)
}
</script>

坑点 6:循环输入框 v-model 绑定同一个变量,全部联动

❌ 错误:

ini 复制代码
<input v-for="item in list" :key="item.id" v-model="inputVal" />

所有输入框绑定同一个变量,改一个全部一起变。

推荐写法:

绑定循环项自身属性

ini 复制代码
<input v-for="item in list" :key="item.id" v-model="item.name" />

坑点 7:异步接口数据,初始值为 null /undefined,页面直接报错

❌ 错误:

接口还没返回,list 默认是 null,v-for 循环会报迭代器错误:Cannot read property 'Symbol(Symbol.iterator)' of null

css 复制代码
<li v-for="item in list" :key="item.id">{{ item.name }}</li>

推荐方案:

初始值给空数组

csharp 复制代码
const list = ref([])
// 接口返回后再赋值

或者增加空状态判断

xml 复制代码
<ul v-if="list.length">
  <li v-for="item in list" :key="item.id">{{ item.name }}</li>
</ul>
<div v-else>暂无数据</div>

坑点 8:遍历响应式对象,动态新增属性视图不更新

ini 复制代码
const user = reactive({ name: '张三' })
// 直接新增属性,部分场景不触发更新
user.age = 20

解决办法:

替换对象引用

css 复制代码
Object.assign(user, { age: 20 })
// 或者
const newUser = { ...user, age: 20 }
Object.assign(user, newUser)

三、最佳实践总结:

  1. v-for 必须携带 key,优先业务唯一 ID,尽量避免 index
  2. 不要把 v-for 和 v-if 放在同一个标签;过滤列表优先 computed
  3. 尽量避免直接下标修改数组,优先 splice 或者替换引用
  4. 异步列表初始值设置为空数组,防止渲染报错
  5. 需要收集循环组件 ref,使用函数 ref
  6. template 标签用来分组渲染,减少多余 DOM
  7. 模板逻辑保持简洁,复杂计算、过滤交给 script

v-for 看着简单,但是很多坑都是上线操作增删排序的时候才暴露,尤其是动态表单、可拖拽列表场景。只要把 key、v-if 和 v-for 的优先级、响应式更新这三点把控好,基本能避开绝大多数问题。

相关推荐
deli0078 小时前
Redis 缓存穿透、击穿、雪崩:9 组流量实测数据库怎么被打穿
前端
江华森8 小时前
5Mbps 小水管也能扛:K3s 上 k6 压测 + HPA 弹性伸缩实测
前端
shuangyueliao8 小时前
103 个小游戏的商店素材,我一张图都没手动做:截图 / 封面 / 打包 / 提审全自动
前端
用户2763965765108 小时前
放弃 JWT 后,我给 FastAPI 游戏服务端手撸了一套 token session
javascript
ZhengEnCi8 小时前
前端项目为什么需要一份统一的 theme.css:用 CSS 变量管住全局样式与主题切换
前端·css·前端工程化
wordbaby8 小时前
一次微信小程序 `web-view` 跳转原生页回跳后 H5 白屏的排查与修复(Hash 路由污染)
前端
用户721880438358 小时前
密码骰子:结合AIUI在眼镜上生成一串16位密码
前端
江华森9 小时前
全链路可观测落地记:kube-prometheus-stack 在 K3s 上的 8 个坑与一整套修复脚本
前端
用户84298142418109 小时前
从 JavaScript 到可运行的字节码 - ByteJS 使用指南
javascript
deli0079 小时前
27 寸 4K 开 125% 为什么发虚?PPI 和缩放一次算清
前端