跳到主要内容

Vue 列表渲染

列表渲染使用 v-for 指令,根据数据渲染多个相同的元素。本章将详细介绍列表渲染的各种用法。

基本用法

遍历数组

<ul>
<li v-for="item in items">{{ item }}</li>
</ul>
<script setup>
import { ref } from 'vue'
const items = ref(['苹果', '香蕉', '橙子'])
</script>

带索引的遍历

<ul>
<li v-for="(item, index) in items">
{{ index }}. {{ item }}
</li>
</ul>

v-for 中的 key

为了给 Vue 一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,你需要为每个项目提供一个唯一的 key 属性:

<ul>
<li v-for="item in items" :key="item.id">
{{ item.name }}
</li>
</ul>
<script setup>
import { ref } from 'vue'
const items = ref([
{ id: 1, name: '苹果' },
{ id: 2, name: '香蕉' },
{ id: 3, name: '橙子' }
])
</script>

为什么需要 key?

  1. 高效更新:Vue 可以精确知道哪个元素发生了变化
  2. 维护状态:如果列表中有输入框,key 可以帮助维护输入框状态
  3. 动画效果:列表变化时可以有更好的过渡效果
提示

key 应该是唯一的,通常使用 id 而不是数组索引。如果使用索引作为 key,当列表发生变化时可能导致问题。

遍历对象

遍历对象属性

<ul>
<li v-for="value in object">{{ value }}</li>
</ul>
<script setup>
import { ref } from 'vue'
const object = ref({
name: '张三',
age: 25,
city: '北京'
})
</script>

遍历对象属性(带键和索引)

<ul>
<li v-for="(value, key, index) in object">
{{ index }}. {{ key }}: {{ value }}
</li>
</ul>

输出结果:

0. name: 张三
1. age: 25
2. city: 北京

遍历数字

Vue 可以遍历数字:

<ul>
<li v-for="n in 5">{{ n }}</li>
</ul>

输出:

1
2
3
4
5

在 template 上使用 v-for

如果想渲染多个元素块,可以使用 <template>

<template v-for="item in items">
<li>{{ item.name }}</li>
<li class="divider"></li>
</template>

v-for 与 v-if 一起使用

注意:不推荐在同一元素上同时使用 v-forv-if

如果需要过滤,推荐使用计算属性:

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

const items = ref([
{ name: '苹果', show: true },
{ name: '香蕉', show: false },
{ name: '橙子', show: true }
])

// 使用计算属性过滤
const visibleItems = computed(() => {
return items.value.filter(item => item.show)
})
</script>

<template>
<ul>
<li v-for="item in visibleItems" :key="item.name">
{{ item.name }}
</li>
</ul>
</template>

数组变化检测

Vue 包装了常用的数组变异方法,调用这些方法会触发视图更新:

变异方法

这些方法会改变原数组,会触发视图更新:

const items = ref(['a', 'b', 'c'])

// push() - 添加元素
items.value.push('d')

// pop() - 移除最后一个元素
items.value.pop()

// shift() - 移除第一个元素
items.value.shift()

// unshift() - 添加到开头
items.value.unshift('0')

// splice() - 添加/删除/替换
items.value.splice(1, 1) // 删除索引1的元素
items.value.splice(1, 0, 'x') // 在索引1插入x

// sort() - 排序
items.value.sort()

// reverse() - 反转
items.value.reverse()

非变异方法

这些方法返回新数组,需要赋值给原数组:

const items = ref([1, 2, 3])

// filter() - 过滤
const filtered = items.value.filter(item => item > 1)
items.value = filtered

// map() - 映射
const mapped = items.value.map(item => item * 2)
items.value = mapped

// concat() - 合并
items.value = items.value.concat([4, 5])

// slice() - 切片
items.value = items.value.slice(0, 2)

示例:待办事项列表

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

const newTodo = ref('')
const todos = ref([
{ id: 1, text: '学习 Vue', done: false },
{ id: 2, text: '做作业', done: true },
{ id: 3, text: '锻炼身体', done: false }
])

let nextId = 4

function addTodo() {
if (newTodo.value.trim()) {
todos.value.push({
id: nextId++,
text: newTodo.value,
done: false
})
newTodo.value = ''
}
}

function removeTodo(index) {
todos.value.splice(index, 1)
}

function toggleTodo(todo) {
todo.done = !todo.done
}
</script>

<template>
<div class="todo-list">
<h2>待办事项</h2>

<div class="input-group">
<input
v-model="newTodo"
@keyup.enter="addTodo"
placeholder="添加新事项"
/>
<button @click="addTodo">添加</button>
</div>

<ul>
<li
v-for="(todo, index) in todos"
:key="todo.id"
:class="{ done: todo.done }"
>
<input
type="checkbox"
:checked="todo.done"
@change="toggleTodo(todo)"
/>
<span>{{ todo.text }}</span>
<button class="delete-btn" @click="removeTodo(index)">删除</button>
</li>
</ul>

<p v-if="todos.length === 0">暂无待办事项</p>
<p>共 {{ todos.length }} 项</p>
</div>
</template>

<style scoped>
.todo-list {
max-width: 400px;
margin: 0 auto;
}
.input-group {
display: flex;
gap: 10px;
margin-bottom: 20px;
}
input {
flex: 1;
padding: 8px;
}
button {
padding: 8px 16px;
}
ul {
list-style: none;
padding: 0;
}
li {
display: flex;
align-items: center;
padding: 10px;
border-bottom: 1px solid #eee;
}
li.done span {
text-decoration: line-through;
color: #999;
}
.delete-btn {
margin-left: auto;
background: #ff4444;
color: white;
border: none;
}
</style>

示例:搜索过滤

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

const searchText = ref('')
const items = ref([
{ id: 1, name: '苹果', category: '水果' },
{ id: 2, name: '香蕉', category: '水果' },
{ id: 3, name: '胡萝卜', category: '蔬菜' },
{ id: 4, name: '西兰花', category: '蔬菜' },
{ id: 5, name: '草莓', category: '水果' }
])

const filteredItems = computed(() => {
if (!searchText.value) return items.value
return items.value.filter(item =>
item.name.toLowerCase().includes(searchText.value.toLowerCase())
)
})
</script>

<template>
<div class="search-demo">
<input
v-model="searchText"
placeholder="搜索..."
/>

<ul>
<li v-for="item in filteredItems" :key="item.id">
{{ item.name }} - {{ item.category }}
</li>
</ul>

<p v-if="filteredItems.length === 0">没有找到匹配项</p>
</div>
</template>

<style scoped>
.search-demo {
max-width: 400px;
}
input {
width: 100%;
padding: 10px;
margin-bottom: 20px;
}
ul {
list-style: none;
padding: 0;
}
li {
padding: 10px;
border-bottom: 1px solid #eee;
}
</style>

小结

本章我们学习了 Vue 列表渲染的完整内容:

  1. v-for 基本用法:遍历数组和对象
  2. key 属性:为什么需要 key 及如何使用
  3. 遍历数字:v-for 也可以遍历数字
  4. 数组变化检测:变异方法和非变异方法
  5. v-for 与 v-if:推荐使用计算属性过滤
  6. 实际示例:待办事项列表和搜索过滤

练习

  1. 创建一个水果列表,包含名称和价格
  2. 实现列表的排序功能(按名称、价格排序)
  3. 实现分页功能(每页显示5条)

准备好进入下一章,学习事件处理的内容了吗?