第 5 节:条件渲染与列表渲染
📚 本节目标
- 掌握 v-if、v-else-if、v-else 的使用
- 理解 v-if 和 v-show 的区别
- 掌握 v-for 循环渲染列表
- 理解 key 属性的作用
一、条件渲染:v-if / v-else
什么是条件渲染?
大白话:根据条件决定某个元素显示还是隐藏。
就像日常生活:
- 如果下雨 → 带伞
- 如果不下雨 → 不带伞
在 Vue 中:
- 如果条件为真 → 显示元素
- 如果条件为假 → 不显示元素
基本用法
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>v-if 基础示例</title>
<style>
.container {
max-width: 600px;
margin: 50px auto;
padding: 20px;
}
.message {
padding: 15px;
border-radius: 5px;
margin: 10px 0;
}
.success {
background: #d4edda;
color: #155724;
}
.error {
background: #f8d7da;
color: #721c24;
}
</style>
</head>
<body>
<div id="app">
<div class="container">
<h2>登录状态</h2>
<button @click="isLoggedIn = !isLoggedIn">
{{ isLoggedIn ? '退出登录' : '登录' }}
</button>
<!-- v-if:条件为 true 时显示 -->
<div v-if="isLoggedIn" class="message success">
✅ 欢迎回来!你已经登录了。
</div>
<!-- v-else:条件为 false 时显示 -->
<div v-else class="message error">
❌ 你还没有登录,请先登录。
</div>
</div>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
const { createApp } = Vue;
createApp({
data() {
return {
isLoggedIn: false // 初始状态:未登录
}
}
}).mount('#app');
</script>
</body>
</html>
预期效果:
- 初始显示"你还没有登录"
- 点击按钮后,切换显示"欢迎回来"
- 按钮文字也会跟着变化
关键点:
v-if和v-else必须紧挨着(中间不能有其他元素)v-else不需要写条件,它会自动匹配v-if的相反情况
v-else-if:多条件判断
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>成绩等级判断</title>
<style>
.container {
max-width: 600px;
margin: 50px auto;
padding: 20px;
}
input {
padding: 10px;
font-size: 16px;
margin: 10px 0;
}
.grade {
padding: 20px;
border-radius: 5px;
font-size: 24px;
font-weight: bold;
text-align: center;
margin-top: 20px;
}
.excellent { background: #28a745; color: white; }
.good { background: #17a2b8; color: white; }
.pass { background: #ffc107; color: white; }
.fail { background: #dc3545; color: white; }
</style>
</head>
<body>
<div id="app">
<div class="container">
<h2>成绩等级评定</h2>
<label>请输入分数(0-100):</label>
<input type="number" v-model.number="score" min="0" max="100">
<!-- 多条件判断 -->
<div v-if="score >= 90" class="grade excellent">
🎉 优秀!继续保持!
</div>
<div v-else-if="score >= 80" class="grade good">
😊 良好!再接再厉!
</div>
<div v-else-if="score >= 60" class="grade pass">
👍 及格了!继续努力!
</div>
<div v-else-if="score >= 0" class="grade fail">
😢 不及格,加油!
</div>
<div v-else class="grade" style="background: #ccc;">
请输入有效分数
</div>
</div>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
const { createApp } = Vue;
createApp({
data() {
return {
score: null
}
}
}).mount('#app');
</script>
</body>
</html>
预期效果:
- 输入 95 → 显示"优秀"
- 输入 85 → 显示"良好"
- 输入 65 → 显示"及格"
- 输入 50 → 显示"不及格"
二、v-show:另一种条件渲染
v-if 和 v-show 的区别
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>v-if vs v-show</title>
<style>
.box {
padding: 20px;
margin: 10px;
background: lightblue;
border-radius: 5px;
}
</style>
</head>
<body>
<div id="app">
<button @click="show = !show">切换显示</button>
<p>当前状态:{{ show ? '显示' : '隐藏' }}</p>
<!-- v-if:条件为 false 时,元素完全不存在 -->
<div v-if="show" class="box">
我使用 v-if(条件为 false 时,我会从 DOM 中移除)
</div>
<!-- v-show:条件为 false 时,元素存在但隐藏(display: none) -->
<div v-show="show" class="box">
我使用 v-show(条件为 false 时,我还在 DOM 中,只是隐藏了)
</div>
<p style="color: #999;">
💡 提示:打开浏览器的开发者工具,查看元素的变化
</p>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
const { createApp } = Vue;
createApp({
data() {
return {
show: true
}
}
}).mount('#app');
</script>
</body>
</html>
对比总结
| 特性 | v-if | v-show |
|---|---|---|
| 原理 | 条件为 false 时,元素不会被渲染到 DOM | 条件为 false 时,元素仍在 DOM,只是 display: none |
| 切换开销 | 高(每次都要创建/销毁元素) | 低(只是修改 CSS) |
| 初始开销 | 低(条件为 false 时不渲染) | 高(始终会渲染) |
| 使用场景 | 不频繁切换的情况 | 频繁切换的情况 |
| 配套指令 | 可以配合 v-else 使用 | 不能配合 v-else |
记忆技巧:
- 频繁切换 → 用
v-show(就像开关灯,灯泡一直在,只是开关控制) - 不常切换 → 用
v-if(就像拆装家具,需要就装上,不需要就拆掉)
三、列表渲染:v-for
基本用法:循环数组
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>v-for 基础示例</title>
<style>
.container {
max-width: 600px;
margin: 50px auto;
padding: 20px;
}
.fruit-list {
list-style: none;
padding: 0;
}
.fruit-item {
padding: 15px;
margin: 10px 0;
background: #f0f0f0;
border-radius: 5px;
border-left: 4px solid #42b983;
}
</style>
</head>
<body>
<div id="app">
<div class="container">
<h2>我喜欢的水果</h2>
<!-- v-for 循环数组 -->
<ul class="fruit-list">
<li
v-for="fruit in fruits"
:key="fruit"
class="fruit-item">
🍎 {{ fruit }}
</li>
</ul>
</div>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
const { createApp } = Vue;
createApp({
data() {
return {
fruits: ['苹果', '香蕉', '橙子', '葡萄', '西瓜']
}
}
}).mount('#app');
</script>
</body>
</html>
预期效果:
- 显示 5 个水果名称
- 每个水果一行
语法解释:
<li v-for="fruit in fruits" :key="fruit">
fruit:循环时的单个元素(可以自己命名)fruits:要循环的数组名:key:给每个元素一个唯一标识(后面详解)
获取索引(下标)
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>v-for 获取索引</title>
</head>
<body>
<div id="app">
<div class="container">
<h2>排行榜</h2>
<!-- (item, index) 可以同时获取元素和索引 -->
<ul>
<li v-for="(player, index) in players" :key="index">
第 {{ index + 1 }} 名:{{ player }}
</li>
</ul>
</div>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
const { createApp } = Vue;
createApp({
data() {
return {
players: ['小明', '小红', '小刚', '小美']
}
}
}).mount('#app');
</script>
</body>
</html>
预期效果:
第 1 名:小明
第 2 名:小红
第 3 名:小刚
第 4 名:小美
注意:index 是从 0 开始的,所以显示时要 +1。
循环对象数组(常用)
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>循环对象数组</title>
<style>
.student-card {
border: 2px solid #42b983;
border-radius: 10px;
padding: 20px;
margin: 15px 0;
background: #f9f9f9;
}
.student-card h3 {
margin-top: 0;
color: #42b983;
}
</style>
</head>
<body>
<div id="app">
<div class="container">
<h2>👨🎓 学生名单</h2>
<!-- 循环对象数组 -->
<div
v-for="student in students"
:key="student.id"
class="student-card">
<h3>{{ student.name }}</h3>
<p>年龄:{{ student.age }} 岁</p>
<p>成绩:{{ student.score }} 分</p>
</div>
</div>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
const { createApp } = Vue;
createApp({
data() {
return {
students: [
{ id: 1, name: '小明', age: 18, score: 95 },
{ id: 2, name: '小红', age: 17, score: 88 },
{ id: 3, name: '小刚', age: 19, score: 92 }
]
}
}
}).mount('#app');
</script>
</body>
</html>
预期效果:
- 显示 3 个学生卡片
- 每个卡片包含姓名、年龄、成绩
四、key 属性的作用(重要)
为什么需要 key?
大白话:key 就像给每个元素一个身份证号,让 Vue 能准确识别它们。
不加 key 的问题:
<!-- 没有 key -->
<li v-for="item in list">{{ item }}</li>
- Vue 不知道哪个元素是哪个
- 数据更新时可能出错(比如删除错了元素)
加了 key 的好处:
<!-- 有 key -->
<li v-for="item in list" :key="item.id">{{ item }}</li>
- Vue 可以准确追踪每个元素
- 更新效率更高、更准确
key 的使用规则
✅ 正确的 key:
<!-- 用唯一的 id -->
<li v-for="item in items" :key="item.id">
<!-- 用索引(仅在简单场景) -->
<li v-for="(item, index) in items" :key="index">
❌ 错误的 key:
<!-- 不要用随机数 -->
<li v-for="item in items" :key="Math.random()">
<!-- 不要用重复的值 -->
<li v-for="item in items" :key="item.name"> <!-- 如果名字重复就不行 -->
最佳实践:
- 如果数据有唯一 ID,就用 ID 作为 key
- 如果只是简单显示、不涉及增删改,可以用索引
五、条件渲染 + 列表渲染:结合使用
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>任务管理</title>
<style>
.container {
max-width: 700px;
margin: 50px auto;
padding: 20px;
}
.filter-buttons {
margin: 20px 0;
}
.filter-buttons button {
padding: 10px 20px;
margin-right: 10px;
border: none;
border-radius: 5px;
cursor: pointer;
background: #f0f0f0;
}
.filter-buttons button.active {
background: #42b983;
color: white;
}
.task-list {
list-style: none;
padding: 0;
}
.task-item {
padding: 15px;
margin: 10px 0;
background: #f9f9f9;
border-radius: 5px;
display: flex;
justify-content: space-between;
align-items: center;
}
.task-item.completed {
text-decoration: line-through;
opacity: 0.6;
}
.empty {
text-align: center;
color: #999;
padding: 50px;
}
</style>
</head>
<body>
<div id="app">
<div class="container">
<h2>📋 任务管理器</h2>
<!-- 筛选按钮 -->
<div class="filter-buttons">
<button
@click="filter = 'all'"
:class="{ active: filter === 'all' }">
全部 ({{ tasks.length }})
</button>
<button
@click="filter = 'active'"
:class="{ active: filter === 'active' }">
未完成 ({{ activeTasks.length }})
</button>
<button
@click="filter = 'completed'"
:class="{ active: filter === 'completed' }">
已完成 ({{ completedTasks.length }})
</button>
</div>
<!-- 任务列表 -->
<ul class="task-list" v-if="filteredTasks.length > 0">
<li
v-for="task in filteredTasks"
:key="task.id"
:class="{ 'task-item': true, 'completed': task.completed }">
<span>{{ task.text }}</span>
<button @click="toggleTask(task.id)">
{{ task.completed ? '恢复' : '完成' }}
</button>
</li>
</ul>
<!-- 空状态 -->
<div v-else class="empty">
<p>{{ emptyMessage }}</p>
</div>
</div>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
const { createApp } = Vue;
createApp({
data() {
return {
filter: 'all', // 当前筛选条件
tasks: [
{ id: 1, text: '学习 Vue 基础', completed: true },
{ id: 2, text: '完成练习题', completed: false },
{ id: 3, text: '做一个小项目', completed: false },
{ id: 4, text: '复习知识点', completed: true }
]
}
},
computed: {
// 未完成的任务
activeTasks() {
return this.tasks.filter(task => !task.completed);
},
// 已完成的任务
completedTasks() {
return this.tasks.filter(task => task.completed);
},
// 根据筛选条件显示的任务
filteredTasks() {
if (this.filter === 'active') {
return this.activeTasks;
} else if (this.filter === 'completed') {
return this.completedTasks;
}
return this.tasks;
},
// 空状态提示
emptyMessage() {
if (this.filter === 'active') {
return '🎉 太棒了!没有未完成的任务!';
} else if (this.filter === 'completed') {
return '还没有完成任何任务';
}
return '还没有任务';
}
},
methods: {
// 切换任务状态
toggleTask(id) {
const task = this.tasks.find(t => t.id === id);
if (task) {
task.completed = !task.completed;
}
}
}
}).mount('#app');
</script>
</body>
</html>
预期效果:
- 点击"全部":显示所有任务
- 点击"未完成":只显示未完成的任务
- 点击"已完成":只显示已完成的任务
- 点击"完成"按钮:任务被划掉
- 如果某个类别没有任务,显示提示信息
注意:这里用到了 computed 计算属性,我们会在后面的课程详细讲解。
六、本节重点回顾
✅ v-if / v-else-if / v-else:条件渲染,元素根据条件显示/隐藏
✅ v-show:通过 CSS 控制显示/隐藏,适合频繁切换
✅ v-for:循环渲染列表,语法 item in items
✅ :key:给循环的元素添加唯一标识,提高性能和准确性
✅ 可以结合使用 v-if 和 v-for,实现复杂的列表筛选
七、练习题
练习 1:商品列表
创建一个商品列表,要求:
- 显示商品名称和价格
- 如果价格 > 100,显示"💰 高价商品"标签
- 如果价格 < 50,显示"🔥 特价"标签
数据参考:
products: [
{ id: 1, name: '手机', price: 3999 },
{ id: 2, name: '耳机', price: 299 },
{ id: 3, name: '数据线', price: 29 },
{ id: 4, name: '充电器', price: 99 }
]
点击查看答案
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>商品列表</title>
<style>
.product-item {
padding: 15px;
margin: 10px;
border: 1px solid #ddd;
border-radius: 5px;
}
.tag {
display: inline-block;
padding: 3px 10px;
border-radius: 3px;
font-size: 12px;
margin-left: 10px;
}
.expensive {
background: #ff6b6b;
color: white;
}
.cheap {
background: #51cf66;
color: white;
}
</style>
</head>
<body>
<div id="app">
<h2>商品列表</h2>
<div v-for="product in products" :key="product.id" class="product-item">
<strong>{{ product.name }}</strong>
<span>¥{{ product.price }}</span>
<span v-if="product.price > 100" class="tag expensive">💰 高价商品</span>
<span v-if="product.price < 50" class="tag cheap">🔥 特价</span>
</div>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
const { createApp } = Vue;
createApp({
data() {
return {
products: [
{ id: 1, name: '手机', price: 3999 },
{ id: 2, name: '耳机', price: 299 },
{ id: 3, name: '数据线', price: 29 },
{ id: 4, name: '充电器', price: 99 }
]
}
}
}).mount('#app');
</script>
</body>
</html>
练习 2:动态添加删除列表项
在第 4 节的任务清单基础上,添加:
- 每个任务前面显示序号
- 如果任务列表为空,显示提示"还没有任务,快添加一个吧!"
点击查看答案
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>改进的任务清单</title>
</head>
<body>
<div id="app">
<h2>任务清单</h2>
<input type="text" v-model="newTask" @keyup.enter="addTask">
<button @click="addTask">添加</button>
<!-- 任务列表 -->
<ul v-if="tasks.length > 0">
<li v-for="(task, index) in tasks" :key="index">
{{ index + 1 }}. {{ task }}
<button @click="deleteTask(index)">删除</button>
</li>
</ul>
<!-- 空状态 -->
<p v-else style="color: #999;">还没有任务,快添加一个吧!</p>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
const { createApp } = Vue;
createApp({
data() {
return {
newTask: '',
tasks: []
}
},
methods: {
addTask() {
if (this.newTask.trim()) {
this.tasks.push(this.newTask);
this.newTask = '';
}
},
deleteTask(index) {
this.tasks.splice(index, 1);
}
}
}).mount('#app');
</script>
</body>
</html>
🎉 恭喜你完成第五节课!
下一节我们将学习:组件基础 —— 学会创建和使用 Vue 组件,让代码更模块化、更易维护。