Vue3循环语句

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:当前这一项(你可以随便起名,如 itemuserproduct)。
  • 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>

渲染结果示例:

    1. 苹果
    1. 香蕉
    1. 橙子

注意: 这里用了 :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-forv-if,在 Vue 3 里 v-if 会先执行,且可能拿不到预期的循环变量,逻辑容易混乱。
官方建议:不要在同一元素上同时使用 v-for 和 v-if

推荐做法:

  1. <template> 包一层<template> 上写 v-for,子元素上写 v-if
  2. 用计算属性先过滤:在 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 包着的数组做以下操作时,视图会自动更新:

  • 会触发更新的方法pushpopshiftunshiftsplicesort、`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,或计算属性过滤

十三、学习建议

  1. 先练熟:数组 v-for="(item, index) in list" + 始终写 :key="item.id"(或等价唯一值)。
  2. 尽量不用 index 当 key,尤其在列表会增删、排序、过滤时。
  3. v-for 和 v-if 不同时写在同一元素上,用 <template> 或计算属性。
  4. 需要“每轮多块、嵌套循环、在循环里用组件”时,按本文示例套用即可。

把本文档里的示例在项目里敲一遍、改数据看效果,会掌握得更牢。更多指令用法可参考《Vue3指令.md》。祝你学习顺利。

发表评论