在 Web 开发中,渲染列表数据是前端最基础也最频繁的任务之一。过去我们习惯使用原生 JavaScript 操作 DOM,通过循环动态创建元素节点,但随着前端框架的发展,Vue 等现代框架用更高效、声明式的方式重塑了这一过程。
本文将从传统做法讲起,逐步过渡到 Vue 的 v-for,并探讨它的用法场景,帮助你更系统地掌握 Vue 列表渲染的能力。
一、传统做法:手动操作 DOM
我们先来看看最传统的方式:
js
<ul id="todo-list"></ul>
<script>
const todos = [
{ name: '学习 JavaScript' },
{ name: '了解 DOM 操作' },
{ name: '尝试 Vue 框架' }
];
const ul = document.getElementById('todo-list');
todos.forEach(todo => {
const li = document.createElement('li');
li.textContent = todo.name;
ul.appendChild(li);
});
</script>
这段代码做了什么?
-
手动获取 DOM 元素
-
遍历数组并为每一项创建 DOM 元素
-
插入到页面中
虽然清晰,但一旦数据变化(比如添加、删除项),就必须重新操作 DOM,极其繁琐、容易出错,而且性能难以保证。
二、Vue 的声明式语法:v-for 入门
Vue 使用 v-for 指令让列表渲染变得优雅自然:
js
<template>
<ul>
<li v-for="item in items" :key="item.id">
{{ item.name }}
</li>
</ul>
</template>
<script setup>
import { ref } from 'vue'
const items = ref([
{ id: 1, name: '学习 JavaScript' },
{ id: 2, name: '了解 Vue' },
{ id: 3, name: '构建项目' }
])
</script>
🧠 解读:
-
v-for="item in items":遍历响应式数组 items
-
:key="item.id":为每个元素设置唯一 key,便于 Vue 高效地追踪变化
-
使用 ref 创建响应式数据源
优点:无需关心 DOM 操作,数据变动自动驱动视图更新。
三、变量作用域与索引值
v-for 支持访问索引值,还能使用父级作用域的变量:
js
<template>
<ul>
<li v-for="(item, index) in items" :key="item.id">
{{ parentLabel }} - {{ index + 1 }}: {{ item.name }}
</li>
</ul>
</template>
<script setup>
const parentLabel = '待办事项'
const items = ref([
{ id: 1, name: '写文章' },
{ id: 2, name: '整理笔记' }
])
</script>
四、对象的遍历
不仅数组可以遍历,对象同样适用:
js
<template>
<ul>
<li v-for="(value, key, index) in userInfo" :key="key">
{{ index }}. {{ key }}: {{ value }}
</li>
</ul>
</template>
<script setup>
import { reactive } from 'vue'
const userInfo = reactive({
name: '小明',
age: 25,
city: '北京'
})
</script>
五、基于范围的遍历(整数渲染)
js
<template>
<span v-for="n in 5" :key="n">{{ n }} </span>
</template>
输出:1 2 3 4 5
注意:n 从 1 开始,而不是 0。
六、嵌套 v-for 与作用域访问
js
<template>
<ul>
<li v-for="group in groups" :key="group.title">
<h3>{{ group.title }}</h3>
<ul>
<li v-for="user in group.users" :key="user.id">
{{ user.name }}
</li>
</ul>
</li>
</ul>
</template>
<script setup>
const groups = ref([
{
title: '开发组',
users: [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }]
},
{
title: '产品组',
users: [{ id: 3, name: 'Cathy' }]
}
])
</script>
子作用域可以访问父级变量,如 group.title。
七、v-if 与 v-for 的冲突与解决
js
<!-- 错误示范:todo 未定义 -->
<li v-for="todo in todos" v-if="!todo.done">
{{ todo.text }}
</li>
修复方式:
js
<template v-for="todo in todos" :key="todo.id">
<li v-if="!todo.done">
{{ todo.text }}
</li>
</template>
或者使用计算属性预先过滤:
js
const visibleTodos = computed(() => todos.value.filter(todo => !todo.done))
八、组件中使用 v-for 并传参
js
<template>
<TodoItem
v-for="(todo, index) in todos"
:key="todo.id"
:item="todo"
:index="index"
/>
</template>
<script setup>
import TodoItem from './TodoItem.vue'
const todos = ref([
{ id: 1, text: '学 Vue' },
{ id: 2, text: '写博客' }
])
</script>
TodoItem.vue:
js
<template>
<li>{{ index + 1 }}. {{ item.text }}</li>
</template>
<script setup>
defineProps(['item', 'index'])
</script>
九、响应式数组变化与不可变操作
js
// ✅ 可侦测的变更方法
items.value.push({ id: 4, name: '复习知识点' })
// ✅ 替换为新数组(推荐)
items.value = items.value.filter(item => item.name.includes('Vue'))
计算属性过滤:
js
const filteredItems = computed(() => {
return items.value.filter(item => item.name.includes('Vue'))
})
避免修改原数组:
js
// ❌ 会改变原数组
// return items.value.reverse()
// ✅ 安全做法
return [...items.value].reverse()
🔚 总结:Vue 的 v-for 相比传统方法的优势
维度 | 传统 JavaScript DOM 操作 | Vue 的 v-for |
---|---|---|
语法简洁 | ❌ 需手动创建元素、插入 DOM | ✅ 声明式语法 |
数据响应 | ❌ 修改数据不自动更新视图 | ✅ 响应式更新 |
可维护性 | ❌ 容易出错、难以维护 | ✅ 易读、易维护 |
组件复用 | ❌ 手动处理数据与 DOM 分离 | ✅ 配合组件优雅封装 |
性能优化 | ❌ 手动管理元素重排 | ✅ Vue 自动 diff 算法优化更新 |
作用域访问 | ❌ 局部作用域难控制 | ✅ 嵌套作用域清晰 |
📌 写在最后
从手动构建列表到使用 Vue 的 v-for,我们经历的是从命令式到声明式、从混乱到有序的技术演进。这不仅提升了开发效率,也让代码更具表达力和可维护性。