Vue.js: 条件渲染与列表渲染
最后更新:2026-10-20
条件渲染(v-if / v-show)和列表渲染(v-for)是 Vue 模板最常用的两个指令。前者控制"显示不显示",后者控制"显示多少个"。两者组合能渲染 90% 的 UI 场景。
掌握 v-if 和 v-for 的 5 大区别,以及 :key 的正确用法,是写出高性能 Vue 应用的关键。
1. 你将学到
v-if/v-else-if/v-else条件渲染v-showvsv-if的 5 大区别v-for遍历数组、对象、数字、字符串:key的作用和最佳实践template元素包装多个元素- 列表过滤、排序、分页
- 虚拟滚动优化(10000+ 项)
2. 一个仪表盘"if 套娃"的代码灾难
(1) 痛点:5 个 v-if 嵌套,调试地狱
Alice 的仪表盘代码失控了:
Charlie 的代码审查意见:
"Alice,这根本没法读。5 层 v-if 嵌套。再加 2 个条件怎么办?我们需要更清晰的模式。"
(2) Vue 解法:v-if 扁平化 + 计算属性
(3) 收益
- 可读性:5 层嵌套 → 6 行平铺
- 可维护:加条件只需加一行 v-else-if
- 调试:每行独立条件,便于打断点
3. v-if / v-else-if / v-else 条件渲染
(1) 基本语法
(2) template 元素
(3) 5 大使用场景
| 场景 | 用 v-if |
|---|---|
| 登录/未登录 | ✅ |
| 加载中/加载完成 | ✅ |
| 空数据/有数据 | ✅ |
| 错误状态 | ✅ |
| 频繁切换显示/隐藏 | ❌(用 v-show) |
▶ 示例:5 种 v-if/v-show 写法
▶ 试一试输出:
TEXT
1. v-if 显示(点击切换)
2. v-show 显示
3. 类型 A
4. Admin Panel + Edit 按钮 ← 5a 不渲染,只显示 5b
点"切换 show" → 第 1、2 行消失;点"切换 type" → 第 3 行变"3. 类型 B"
4. v-show vs v-if 5 大区别
(1) 核心区别
(2) 5 大对比
| 维度 | v-if | v-show |
|---|---|---|
| DOM 操作 | 条件 false 时移除元素 | 始终在 DOM,只切 display |
| 切换性能 | 差(每次销毁/重建) | 好(只切 CSS) |
| 初始渲染 | 条件 false 时不渲染 | 始终渲染(即使不可见) |
| 配合 transition | ✅ 完美(动画进出) | ❌ 不支持 |
| 适用场景 | 偶尔切换 | 频繁切换 |
(3) 选择建议
JS
// 偶尔切换(如登录/未登录)→ v-if
<div v-if="isLoggedIn">Welcome, {{ user.name }}</div>
<div v-else>Please login</div>
// 频繁切换(如 tab 切换)→ v-show
<button v-show="isActive">Active</button>
<button v-show="!isActive">Inactive</button>
5. v-for 列表渲染
(1) 遍历数组
(2) 遍历对象
(3) 遍历数字
(4) 遍历字符串
▶ 示例:5 种 v-for 写法
▶ 试一试输出:
TEXT
1. 数组 → Apple / Banana / Cherry
2. 数组 + 索引 → 1. Apple / 2. Banana / 3. Cherry
3. 对象 → name: Alice / age: 25 / email: alice@example.com
4. 数字 → [1][2][3][4][5]
5. 字符串 → H e l l o
▶ 示例:v-if 和 v-for 优先级
▶ 试一试输出:
TEXT
列表只有 Alice(Bob 的 isActive 为 false,被 computed 过滤掉)
点"切换 Alice 状态" → 列表清空
再点一次 → Alice 重新出现在列表里
⚠️ Vue 3 中 v-if 优先级高于 v-for(Vue 2 相反)。官方推荐:不要一起用,用 template 分开或 computed 过滤。
6. :key 的作用和最佳实践
(1) :key 是什么?
:key 给每个 v-for 项一个唯一标识,帮助 Vue 高效更新 DOM。
(2) 为什么需要 :key?
graph LR
A[原列表 A B C] --> B[新列表 B C D]
B --> C[没有 key:可能错位]
B --> D[有 key:正确更新]
style C fill:#ff6b6b
style D fill:#42b883
没有 :key 时:
- Vue 用 index 比较
- 删除 A 后,B/C 变成 (0)/(1),但 Vue 以为是 "B/C 还在"
- 导致组件状态错位
有 :key 时:
- Vue 用 id 比较
- 精确识别哪些新增/删除/移动
- 性能最优
(3) :key 最佳实践
VUE
<!-- ✅ 推荐:唯一 ID -->
<li v-for="user in users" :key="user.id">
<!-- ✅ 次选:唯一字段(邮箱、手机号等)-->
<li v-for="user in users" :key="user.email">
<!-- ❌ 避免:index(数据变化时会错位)-->
<li v-for="(user, index) in users" :key="index">
<!-- ❌ 绝对不要:random()(每次渲染都不同,强制重建)-->
<li v-for="user in users" :key="Math.random()">
▶ 示例:5 种 :key 用法对比
▶ 试一试输出:
TEXT
1、2 两段都渲染 Alice / Bob
3. 复合 key → Post A / Post B
4. index 当 key → X / Y
5. random() 当 key → X / Y 但每次渲染 key 都变,DOM 被整体重建
7. 列表过滤 / 排序 / 分页
(1) 过滤 + 排序(计算属性)
JS
const { ref, computed } = Vue
const items = ref([
{ id: 1, name: 'iPhone', price: 999, category: 'phone' },
{ id: 2, name: 'MacBook', price: 2499, category: 'laptop' },
{ id: 3, name: 'iPad', price: 599, category: 'tablet' }
])
const searchQuery = ref('')
const selectedCategory = ref('all')
const sortBy = ref('price') // 'price' / 'name'
// 过滤 + 排序(computed 自动缓存)
const filteredItems = computed(() => {
let result = items.value
// 1. 按搜索词过滤
if (searchQuery.value) {
result = result.filter(item =>
item.name.toLowerCase().includes(searchQuery.value.toLowerCase())
)
}
// 2. 按分类过滤
if (selectedCategory.value !== 'all') {
result = result.filter(item => item.category === selectedCategory.value)
}
// 3. 排序
result = [...result].sort((a, b) => {
if (sortBy.value === 'price') return a.price - b.price
if (sortBy.value === 'name') return a.name.localeCompare(b.name)
return 0
})
return result
})
(2) 分页
JS
const currentPage = ref(1)
const pageSize = ref(10)
// 分页后的数据
const paginatedItems = computed(() => {
const start = (currentPage.value - 1) * pageSize.value
return filteredItems.value.slice(start, start + pageSize.value)
})
// 总页数
const totalPages = computed(() =>
Math.ceil(filteredItems.value.length / pageSize.value)
)
8. 完整示例:动态商品列表
▶ 示例:列表过滤 + 排序 + 分页完整示例
▶ 展开运行示例(67 行)
输出:
TEXT
搜索框为空,分类选"全部",排序选"价格"
列表按价格升序:iPad - $599 / iPhone - $999 / MacBook - $2499
页码显示 1 / 1,"上一页""下一页"都置灰不可点
输入 "Mac" → 列表只剩 MacBook - $2499,页码仍 1 / 1
(1) 📊 5 个常见错误
| 错误 | 现象 | 解决 |
|---|---|---|
| 没有 :key | 列表错位、性能差 | 加 :key="item.id" |
| v-for 和 v-if 一起用 | v-if 优先级混乱 | 用 template 包装或 computed |
| 用 index 当 key | 排序时错位 | 用唯一 ID |
v-show 不支持 <transition> |
不能动画 | 改用 v-if + <transition> |
| 列表太大(10万+) | 页面卡死 | 用 vue-virtual-scroller 虚拟滚动 |
❓ 常见问题
Q v-if 和 v-show 怎么选?
A 偶尔切换用 v-if(节省初始渲染),频繁切换用 v-show(避免反复销毁/重建 DOM)。登录/未登录用 v-if,tab 切换用 v-show。
Q v-for 必须用 :key 吗?
A 强烈推荐用 :key 避免性能问题和组件状态错位。用唯一 ID(item.id)最佳,index 次之,绝对不要用 random()。
Q v-if 和 v-for 一起用,优先级?
A Vue 3 中 v-if 优先级高于 v-for(Vue 2 相反)。但官方推荐不要一起用,用 template 分开或用 computed 过滤。
Q template 元素有什么用?
A template 是 Vue 逻辑包装元素,不渲染到 DOM。用于 v-if/v-for 包含多个元素时,避免多余的 div。
Q 列表性能差怎么办?
A 3 个优化:(1) 加 :key;(2) 用 computed 缓存过滤/排序结果;(3) 大列表(10万+)用 vue-virtual-scroller 虚拟滚动,只渲染可见区域。
Q v-for 遍历对象能拿到 key/value 吗?
A 能。
v-for="(value, key, index) in obj",参数顺序:值、键、索引。Q 怎么实现"无数据时显示提示"?
A 用 v-if/v-else 组合:
<div v-if="items.length">列表</div><div v-else>无数据</div>。Q 列表点击项高亮怎么实现?
A 给每个项加 activeIndex 比较:
@click="activeIndex = item.id" :class="{ active: activeIndex === item.id }"。📖 小节
- v-if 条件渲染(移除 DOM),v-show 切换 display(始终在 DOM)
- 偶尔切换用 v-if,频繁切换用 v-show
- v-for 遍历数组/对象/数字/字符串,用 :key 提升性能
- :key 用唯一 ID 最佳,index 次之,不用 random()
- template 元素包装多个元素,不渲染到 DOM
- 列表过滤/排序/分页用 computed 自动缓存
- Vue 3 中 v-if 优先级高于 v-for
- 大列表用虚拟滚动优化(10万+ 项)
📝 作业
-
基础题(难度⭐) 用 v-if/v-else 实现登录/未登录切换:
- 已登录:显示用户名和"登出"按钮
- 未登录:显示"请登录"和登录表单
- 用 template 包装多元素
-
进阶题(难度⭐⭐) 用 v-for 实现 Todo 列表:
- 3 个状态:全部 / 未完成 / 已完成(3 个按钮切换)
- 列表显示对应状态的任务
- 每项有"完成"和"删除"按钮
- 底部显示"未完成:N 项"
-
挑战题(难度⭐⭐⭐) 实现完整的商品筛选 + 排序 + 分页系统(5 个文件):
- 商品数据:30 个,含 name/price/category/createdAt
- 搜索:按 name 模糊搜索
- 分类筛选:5 个分类(all/phone/laptop/tablet/headphone)
- 排序:价格升序/降序/名称/最新
- 分页:10 项/页,显示页码导航
- 性能:computed 缓存过滤/排序结果
- 大列表优化:1000 项测试,确保不卡