Vue 3 循环语句完全指南
本文档从零开始讲解 Vue 3 里的循环语句:如何根据数组或对象重复渲染多份内容,即 v-for 的方方面面,包括语法、:key、与 v-if 的配合、常见场景和易错点,配有大量示例,适合新手系统学习。
一、为什么需要“循环”?
页面上经常要根据一份数据列表生成多块相似的 HTML,例如:
- 根据商品列表渲染多个商品卡片
- 根据菜单项渲染导航链接
- 根据评论列表渲染多条评论
若数据有 10 条,你不想手写 10 遍相同的标签。在 Vue 里,只要写一遍结构,用 v-for 声明“按哪份数据循环”,Vue 就会自动根据数据的每一项渲染出一份 DOM。
这种“按数据重复渲染”的写法,就是本文要讲的循环语句。
二、v-for 是什么?
v-for 是 Vue 的列表渲染指令:写在要重复的那个元素(或 <template>)上,指定“遍历谁、当前项叫什么、要不要索引”等,Vue 会循环生成多份 DOM。
基本形式:
v-for="(项, 索引?) in 数据源"
- 数据源:通常是数组或对象(也可以是数字范围,见后文)。
- 项:当前这一轮循环拿到的“当前项”(数组元素、对象属性值等)。
- 索引:可选,表示当前是第几项(从 0 开始)。
下面按“遍历数组 → 遍历对象 → 遍历数字 → key → 其他用法”顺序讲。
三、遍历数组
数组是最常用的数据源:每一项对应一个元素,可以拿到当前项和索引。
3.1 基本语法
语法: v-for="(item, index) in list"
- item:当前这一项(你可以随便起名,如
item、user、product)。 - index:当前项的索引,从 0 开始(可选,不需要可以不写)。
- list:你在 script 里定义的数组(ref 或 reactive 的数组)。
<template>
<ul>
<li v-for="(item, index) in fruits" :key="item.id">
{{ index + 1 }}. {{ item.name }}
</li>
</ul>
</template>
<script setup>
import { ref } from 'vue'
const fruits = ref([
{ id: 1, name: '苹果' },
{ id: 2, name: '香蕉' },
{ id: 3, name: '橙子' }
])
</script>
渲染结果示例:
-
- 苹果
-
- 香蕉
-
- 橙子
注意: 这里用了 :key="item.id"。使用 v-for 时强烈建议始终绑定 :key,且 key 要唯一、稳定(见第五节)。
3.2 只写“项”,不写“索引”
若用不到索引,可以只写一个变量:
<template>
<ul>
<li v-for="name in names" :key="name">{{ name }}</li>
</ul>
</template>
<script setup>
import { ref } from 'vue'
const names = ref(['小明', '小红', '小刚'])
</script>
当数组项是简单值(字符串、数字)且没有 id 时,可以用该项本身当 key(前提是项不重复)。若有重复或是对象,仍建议用唯一 id 当 key。
3.3 解构当前项(适合对象数组)
若每一项是对象,可以在 v-for 里直接解构,模板里写起来更短:
<template>
<ul>
<li v-for="({ id, name, price }, index) in products" :key="id">
{{ index + 1 }}. {{ name }} - ¥{{ price }}
</li>
</ul>
</template>
<script setup>
import { ref } from 'vue'
const products = ref([
{ id: 1, name: '苹果', price: 5 },
{ id: 2, name: '香蕉', price: 3 }
])
</script>
四、遍历对象
除了数组,还可以遍历对象:遍历的是对象的可枚举属性,可以拿到属性值、属性名,以及可选的索引。
4.1 基本语法
语法: v-for="(value, key, index) in object"
- value:当前属性的值。
- key:当前属性的名(字符串)。
- index:第几个属性(从 0 开始,可选)。
<template>
<ul>
<li v-for="(value, key) in user" :key="key">
{{ key }}: {{ value }}
</li>
</ul>
</template>
<script setup>
import { ref } from 'vue'
const user = ref({
name: '小明',
age: 18,
city: '北京'
})
</script>
渲染结果示例:
- name: 小明
- age: 18
- city: 北京
对象遍历时,遍历顺序会按“对象属性的创建顺序”,在多数场景下可认为顺序是稳定的,但若对顺序有强要求,更推荐用数组存“键值对”再遍历数组。
4.2 带索引的对象遍历
<template>
<ul>
<li v-for="(value, key, index) in info" :key="key">
第 {{ index + 1 }} 个属性:{{ key }} = {{ value }}
</li>
</ul>
</template>
<script setup>
import { ref } from 'vue'
const info = ref({
a: 1,
b: 2,
c: 3
})
</script>
五、遍历数字范围(从 1 到 N)
语法: v-for="n in 数字"
这里会从 1 循环到该数字(包含),n 依次为 1、2、3、…、数字。注意是 1 开始,不是 0。
<template>
<div>
<span v-for="n in 5" :key="n">{{ n }}</span>
</div>
</template>
渲染结果: 1 2 3 4 5(中间是空格,因为 span 是行内元素)。
适合做“星级显示、简单序号”等,不需要先准备数组。
六、为什么必须写 :key?写什么?
6.1 key 的作用
Vue 在更新“列表”时,需要知道哪一项对应哪一块 DOM,才能正确做复用、移动、增删,避免错乱或重复。
:key 就是给“每一项”一个唯一标识。
key 相同 → Vue 认为是同一条数据 → 会复用对应 DOM;key 变了或新增 → Vue 会新建或移除 DOM。
不写 key 时,Vue 会退化为用“索引”来猜,在有增删、排序的列表里容易出 bug(例如勾选错行、输入错行)。所以只要用 v-for,就应尽量写上 :key。
6.2 key 写什么?唯一且稳定
- 有唯一 id 的列表:用
item.id(或其它唯一字段)当 key。 - 简单值列表且无重复:可以用 该项本身(如
:key="name"),若可能重复则不适合。 - 不推荐用 index 当 key:在“增删、排序、过滤”时,index 会变,导致 Vue 误判“同一条数据”,出现状态错位(例如输入框、勾选状态跑到别的行)。
<!-- 推荐:用唯一 id -->
<li v-for="item in list" :key="item.id">...</li>
<!-- 简单值且不重复时可用项本身 -->
<li v-for="name in names" :key="name">...</li>
<!-- 不推荐:用 index,列表会增删时易出错 -->
<li v-for="(item, index) in list" :key="index">...</li>
6.3 用 index 当 key 会出什么问题?(示例)
假设列表可删除,且每行有一个输入框。用 index 当 key 时,删掉第一项后,原来的“第二项”会变成“第一项”,但 Vue 复用的是“原来第一项”的 DOM,输入框里的字就会错位。
用唯一 id 当 key,删掉一项后,每一项的 key 仍和正确的数据对应,就不会错位。
因此:能不用 index 就不用,优先用业务上的唯一 id。
七、用 循环渲染多块内容
有时“每一轮循环”要渲染多个并列元素(例如一个标题 + 一段描述),可以用 <template> 包住它们,在 <template> 上写 v-for。
<template> 不会生成真实 DOM 节点,只是逻辑上的包装。
<template>
<template v-for="item in list" :key="item.id">
<h3>{{ item.title }}</h3>
<p>{{ item.desc }}</p>
<hr />
</template>
</template>
<script setup>
import { ref } from 'vue'
const list = ref([
{ id: 1, title: '标题一', desc: '描述一' },
{ id: 2, title: '标题二', desc: '描述二' }
])
</script>
每一轮都会渲染一个 <h3>、一个 <p>、一条 <hr>,且 :key 必须写在带 v-for 的节点上(这里就是 <template>)。
八、v-for 和 v-if 不要写在同一元素上
若在同一元素上同时写 v-for 和 v-if,在 Vue 3 里 v-if 会先执行,且可能拿不到预期的循环变量,逻辑容易混乱。
官方建议:不要在同一元素上同时使用 v-for 和 v-if。
推荐做法:
- 用
<template>包一层:<template>上写v-for,子元素上写v-if。 - 用计算属性先过滤:在 script 里得到“过滤后的列表”,模板里只对过滤后的列表做
v-for。
8.1 用 template 包一层
<template>
<template v-for="item in list" :key="item.id">
<p v-if="item.visible">{{ item.name }}</p>
</template>
</template>
<script setup>
import { ref } from 'vue'
const list = ref([
{ id: 1, name: 'A', visible: true },
{ id: 2, name: 'B', visible: false },
{ id: 3, name: 'C', visible: true }
])
</script>
8.2 用计算属性过滤后再循环
<template>
<li v-for="item in visibleList" :key="item.id">{{ item.name }}</li>
</template>
<script setup>
import { ref, computed } from 'vue'
const list = ref([
{ id: 1, name: 'A', visible: true },
{ id: 2, name: 'B', visible: false }
])
const visibleList = computed(() => list.value.filter((item) => item.visible))
</script>
九、数组变化时,Vue 如何更新列表?
Vue 会响应式地监听数组变化。当你对用 ref/reactive 包着的数组做以下操作时,视图会自动更新:
- 会触发更新的方法:
push、pop、shift、unshift、splice、sort、`reverse“ - “替换整个数组”:如
list.value = newList,也会更新。
注意: 直接通过索引改某一项,如 list.value[0] = newItem,在部分情况下可能不会触发视图更新;更稳妥的方式是用 splice 替换,或整份替换数组。若用的是 reactive,直接改 arr[0] = x 是可以被监听到的。
<template>
<div>
<ul>
<li v-for="item in list" :key="item.id">{{ item.name }}</li>
</ul>
<button @click="add">添加</button>
<button @click="removeFirst">删除第一项</button>
</div>
</template>
<script setup>
import { ref } from 'vue'
const list = ref([
{ id: 1, name: '苹果' },
{ id: 2, name: '香蕉' }
])
function add() {
list.value.push({ id: Date.now(), name: '新项' })
}
function removeFirst() {
list.value.shift()
}
</script>
十、常见场景示例
10.1 商品列表(卡片)
<template>
<div class="product-list">
<div v-for="product in products" :key="product.id" class="card">
<img :src="product.img" :alt="product.name" />
<h3>{{ product.name }}</h3>
<p>¥{{ product.price }}</p>
<button @click="addCart(product)">加入购物车</button>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const products = ref([
{ id: 1, name: '苹果', price: 5, img: '/apple.jpg' },
{ id: 2, name: '香蕉', price: 3, img: '/banana.jpg' }
])
function addCart(p) {
console.log('加入', p.name)
}
</script>
<style scoped>
.product-list {
display: flex;
gap: 16px;
flex-wrap: wrap;
}
.card {
border: 1px solid #eee;
padding: 12px;
width: 160px;
}
</style>
10.2 导航菜单(链接列表)
<template>
<nav>
<a
v-for="item in menus"
:key="item.path"
:href="item.path"
:class="{ active: current === item.path }"
>
{{ item.title }}
</a>
</nav>
</template>
<script setup>
import { ref } from 'vue'
const current = ref('/home')
const menus = ref([
{ title: '首页', path: '/home' },
{ title: '关于', path: '/about' },
{ title: '联系', path: '/contact' }
])
</script>
10.3 嵌套循环(二维数据)
<template>
<div>
<div v-for="group in groups" :key="group.id" class="group">
<h3>{{ group.name }}</h3>
<ul>
<li v-for="item in group.items" :key="item.id">
{{ item.name }}
</li>
</ul>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const groups = ref([
{
id: 1,
name: '水果',
items: [
{ id: 1, name: '苹果' },
{ id: 2, name: '香蕉' }
]
},
{
id: 2,
name: '蔬菜',
items: [
{ id: 1, name: '白菜' },
{ id: 2, name: '萝卜' }
]
}
])
</script>
内层循环的 :key 建议用能全局唯一的值(例如 group.id + '-' + item.id),避免不同组下有相同 item.id 时冲突。
10.4 循环里使用组件,并传 :key
在 v-for 里渲染组件时,也要给组件写 :key,并且把当前项通过 props 传进去。
<template>
<div>
<UserCard
v-for="user in users"
:key="user.id"
:user="user"
/>
</div>
</template>
<script setup>
import { ref } from 'vue'
import UserCard from './UserCard.vue'
const users = ref([
{ id: 1, name: '小明', age: 18 },
{ id: 2, name: '小红', age: 20 }
])
</script>
十一、易错点与注意点
11.1 key 必须唯一且稳定
同一份列表里,不能出现重复的 key。重复 key 会导致渲染异常。
key 也尽量不要用随机数(如 Math.random()),否则每次渲染都会变,Vue 无法复用,可能带来性能问题或闪烁。
11.2 循环变量只在当前“循环块”内有效
在 v-for 所在的元素(及其子节点)里,可以直接用“当前项、索引”;出了这个范围就不能再用了。
<template>
<div>
<p v-for="item in list" :key="item.id">
{{ item.name }}
<!-- 这里可以用 item -->
</p>
<!-- 这里没有 item -->
</div>
</template>
11.3 遍历对象时顺序
对象属性遍历顺序一般是“添加顺序”,但若有数字键等,行为可能和预期不完全一致。需要稳定顺序时,建议用数组存 { key, value } 再 v-for 数组。
十二、循环语句速查表
| 需求 | 写法 |
|---|---|
| 遍历数组(项 + 索引) | v-for="(item, index) in list" |
| 遍历数组(仅项) | v-for="item in list" |
| 遍历对象(值 + 键) | v-for="(value, key) in object" |
| 遍历对象(值 + 键 + 索引) | v-for="(value, key, index) in object" |
| 遍历 1 到 N | v-for="n in 10"(n 为 1~10) |
| 绑定 key | :key="item.id"(优先唯一 id) |
| 每轮渲染多块 | <template v-for="..." :key="..."> 包多块 |
| 循环 + 条件 | 用 <template v-for> + 子元素 v-if,或计算属性过滤 |
十三、学习建议
- 先练熟:数组
v-for="(item, index) in list"+ 始终写:key="item.id"(或等价唯一值)。 - 尽量不用 index 当 key,尤其在列表会增删、排序、过滤时。
- v-for 和 v-if 不同时写在同一元素上,用
<template>或计算属性。 - 需要“每轮多块、嵌套循环、在循环里用组件”时,按本文示例套用即可。
把本文档里的示例在项目里敲一遍、改数据看效果,会掌握得更牢。更多指令用法可参考《Vue3指令.md》。祝你学习顺利。