一、条件渲染:控制DOM的显隐逻辑
条件渲染是Vue中根据状态动态添加/移除DOM元素的核心能力,主要通过v-if系列指令和v-show实现。
1.1 v-if:真正的“条件渲染”
v-if是Vue中最基础的条件指令,它会根据表达式的真假,完全创建或销毁对应的DOM元素(包括其内部的组件和事件监听器)。
基础用法
<template>
<div class="user-profile">
<!-- 未登录时显示登录按钮 -->
<button v-if="!isLoggedIn" @click="login">立即登录</button>
<!-- 登录后显示用户信息 -->
<div v-else>
<img src="avatar.png" alt="头像" />
<span>欢迎,{{ username }}!</span>
<button @click="logout">退出登录</button>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
// 登录状态(初始未登录)
const isLoggedIn = ref(false)
// 用户名
const username = ref('')
// 登录逻辑
const login = () => {
isLoggedIn.value = true
username.value = 'Vue新手'
}
// 退出逻辑
const logout = () => {
isLoggedIn.value = false
username.value = ''
}
</script>
多分支条件:v-else-if
当需要多个条件分支时,用v-else-if连接:
<template>
<div class="score-display">
<p v-if="score >= 90">优秀</p>
<p v-else-if="score >= 70">良好</p>
<p v-else-if="score >= 60">及格</p>
<p v-else>不及格</p>
</div>
</template>
<script setup>
import { ref } from 'vue'
const score = ref(85) // 显示“良好”
</script>
批量条件渲染:template
若需要多个元素同时满足条件,可使用template包裹(template不会渲染为真实DOM节点):
<template>
<template v-if="isAdmin">
<button>管理用户</button>
<button>查看日志</button>
<button>设置权限</button>
</template>
</template>
1.2 v-show:基于CSS的“显示切换”
v-show的作用与v-if类似,但它不会销毁DOM元素,而是通过CSS的display: none属性控制显隐。元素始终存在于DOM中,只是“看不见”。
基础用法
<template>
<div class="tab-container">
<button @click="activeTab = 'home'">首页</button>
<button @click="activeTab = 'about'">关于我们</button>
<!-- 用v-show切换tab内容 -->
<div v-show="activeTab === 'home'" class="tab-content">首页内容</div>
<div v-show="activeTab === 'about'" class="tab-content">关于我们</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const activeTab = ref('home') // 默认显示首页
</script>
1.3 v-if vs v-show:核心差异与场景选择
| 特性 | v-if | v-show |
|---|---|---|
| DOM操作 | 销毁/重建元素 | 修改CSS样式(display) |
| 初始渲染成本 | 条件为假时,无成本 | 无论条件真假,都要渲染元素 |
| 切换成本 | 条件切换时,成本高(DOM操作) | 切换成本低(仅修改样式) |
| 应用场景 | 不频繁切换的场景(如登录状态) | 频繁切换的场景(如tab、折叠面板) |
二、列表渲染:遍历数据生成DOM
列表渲染是Vue中将数组/对象数据转换为DOM列表的核心能力,通过v-for指令实现。
2.1 v-for的基本用法
v-for可以遍历数组、对象、字符串、数字,语法为:v-for="(item, index) in data"(数组)或v-for="(value, key, index) in object"(对象)。
1. 遍历数组(最常见)
<template>
<ul class="todo-list">
<!-- 遍历todos数组,item是当前项,index是索引 -->
<li v-for="(todo, index) in todos" :key="todo.id">
{{ index + 1 }}. {{ todo.text }}
<button @click="deleteTodo(index)">删除</button>
</li>
</ul>
</template>
<script setup>
import { ref } from 'vue'
// 待办事项列表
const todos = ref([
{ id: 1, text: '学习Vue条件渲染' },
{ id: 2, text: '掌握v-for的key用法' },
{ id: 3, text: '完成一篇博客' }
])
// 删除待办项
const deleteTodo = (index) => {
todos.value.splice(index, 1)
}
</script>
2. 遍历对象
<template>
<ul class="user-info">
<!-- 遍历user对象,value是属性值,key是属性名 -->
<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: 25, job: '前端开发' })
</script>
3. 遍历字符串/数字
<template>
<!-- 遍历字符串(每个字符) -->
<p v-for="char in 'Vue3'" :key="char">{{ char }}</p>
<!-- 遍历数字(从1到5) -->
<p v-for="num in 5" :key="num">{{ num }}</p>
</template>
2.2 key:列表渲染的“身份证”
key是v-for的必填属性(Vue3会强制报警告),它的作用是给每个列表项一个唯一标识,帮助Vue的虚拟DOM diff算法识别节点,避免错误的复用。
为什么需要key?
Vue的diff算法会复用相同key的节点。若没有key或key重复,Vue会错误地复用节点,导致:
- 输入框内容不更新(如todo项的输入框,删除前面的项后,后面的输入框内容会“串位”);
- 组件状态丢失(如计数器组件,重新排序后计数不会重置)。
正确的key选择
- 优先用数据的唯一ID(如后端返回的
id); - 避免用**索引(index)**作为key(索引会随数组变化而变化,导致key失效)。
错误示例(用index作为key):
<!-- 错误:当数组排序/删除时,index会变化,导致节点复用错误 -->
<li v-for="(todo, index) in todos" :key="index">
{{ todo.text }}
<input type="text" placeholder="备注" />
</li>
正确示例(用唯一ID作为key):
<!-- 正确:用todo.id作为唯一标识,无论数组如何变化,节点都能正确复用 -->
<li v-for="(todo, index) in todos" :key="todo.id">
{{ todo.text }}
<input type="text" placeholder="备注" />
</li>
2.3 v-for与v-if的“正确结合”
Vue3不建议在同一元素上同时使用v-for和v-if(v-for优先级更高,会先遍历所有项再判断条件,导致性能浪费)。
正确的做法是:用computed属性过滤数据,再用v-for遍历过滤后的数组。
示例:显示未完成的待办项
<template>
<ul class="uncompleted-todos">
<!-- 遍历过滤后的未完成列表 -->
<li v-for="todo in uncompletedTodos" :key="todo.id">
{{ todo.text }}
</li>
</ul>
</template>
<script setup>
import { ref, computed } from 'vue'
const todos = ref([
{ id: 1, text: '学习条件渲染', completed: false },
{ id: 2, text: '掌握列表渲染', completed: true },
{ id: 3, text: '写博客', completed: false }
])
// 计算属性:过滤未完成的待办项
const uncompletedTodos = computed(() => {
return todos.value.filter(todo => !todo.completed)
})
</script>
三、实战:结合条件与列表渲染的Todo应用
我们用条件渲染(显示登录状态)+列表渲染(显示待办项)+计算属性(过滤未完成项),实现一个完整的Todo应用:
<template>
<div class="todo-app">
<!-- 登录状态判断 -->
<div v-if="!isLoggedIn" class="login-section">
<input v-model="username" placeholder="用户名" />
<button @click="login">登录</button>
</div>
<!-- 登录后显示Todo列表 -->
<div v-else class="todo-container">
<h2>未完成的任务</h2>
<ul>
<li v-for="todo in uncompletedTodos" :key="todo.id">
{{ todo.text }}
<button @click="markAsCompleted(todo)">完成</button>
</li>
</ul>
<h2>已完成的任务</h2>
<ul class="completed-list">
<li v-for="todo in completedTodos" :key="todo.id">
{{ todo.text }}
<button @click="markAsUncompleted(todo)">撤销</button>
</li>
</ul>
<div class="add-todo">
<input v-model="newTodoText" placeholder="输入新任务" />
<button @click="addTodo">添加</button>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
// 登录状态
const isLoggedIn = ref(false)
const username = ref('')
// 待办项列表
const todos = ref([
{ id: 1, text: '学习条件渲染', completed: false },
{ id: 2, text: '掌握列表渲染', completed: false },
{ id: 3, text: '写博客', completed: true }
])
const newTodoText = ref('')
// 计算属性:未完成的待办项
const uncompletedTodos = computed(() => {
return todos.value.filter(t => !t.completed)
})
// 计算属性:已完成的待办项
const completedTodos = computed(() => {
return todos.value.filter(t => t.completed)
})
// 登录逻辑
const login = () => {
if (username.value.trim()) {
isLoggedIn.value = true
}
}
// 添加待办项
const addTodo = () => {
if (newTodoText.value.trim()) {
todos.value.push({
id: Date.now(), // 用时间戳作为唯一ID
text: newTodoText.value.trim(),
completed: false
})
newTodoText.value = ''
}
}
// 标记为完成
const markAsCompleted = (todo) => {
todo.completed = true
}
// 撤销完成
const markAsUncompleted = (todo) => {
todo.completed = false
}
</script>
<style scoped>
.todo-app { max-width: 600px; margin: 20px auto; }
.login-section { margin-bottom: 20px; }
.completed-list { color: #888; text-decoration: line-through; }
.add-todo { margin-top: 20px; }
</style>
四、课后Quiz:巩固核心知识点
问题1:v-if与v-show的核心区别是什么?分别适合什么场景?
答案:
v-if是销毁/重建DOM(真正的条件渲染),适合不频繁切换的场景(如登录状态);v-show是修改CSS样式(display: none),适合频繁切换的场景(如tab、折叠面板)。
问题2:为什么v-for必须指定key?用index作为key会有什么问题?
答案:
key是Vue识别节点的唯一标识,用于diff算法,确保节点正确复用;- 用
index作为key时,若数组发生排序/新增/删除,index会变化,导致Vue错误地复用节点(如输入框内容“串位”)。
问题3:如何正确结合v-for与v-if?
答案:
- 不建议在同一元素上同时使用(
v-for优先级更高,会先遍历再判断,性能差); - 正确做法:用
computed属性过滤数组(如过滤未完成的todo),再用v-for遍历过滤后的数组。
五、常见报错与解决方案
1. 报错:“v-for requires a key attribute”
- 原因:
v-for渲染的元素未指定唯一key; - 解决:给
v-for的元素添加:key="唯一标识"(如item.id); - 预防:养成写
v-for必加key的习惯。
2. 报错:“Duplicate keys detected: x. This may cause an update error.”
- 原因:
key重复(如多个元素用了相同的id); - 解决:确保
key的唯一性(如用后端返回的id、时间戳); - 预防:避免用
index或重复值作为key。
3. 报错:“v-if and v-for used on the same element”
- 原因:同一元素同时使用
v-for和v-if(Vue3会报警告); - 解决:用
computed属性过滤数组,再用v-for遍历; - 预防:优先用
computed处理条件,再渲染列表。
参考链接:
条件渲染:vuejs.org/guide/essen…
列表渲染:vuejs.org/guide/essen…
key的作用:vuejs.org/guide/essen…
阅读完整的文章:快速入门Vue3:条件渲染与列表渲染的小秘密,你居然还不知道?
往期文章归档
- 快速入门Vue3事件处理的挑战题:v-on、修饰符、自定义事件你能通关吗? - cmdragon's Blog
- 快速入门Vue3的v-指令:数据和DOM的“翻译官”到底有多少本事? - cmdragon's Blog
- 快速入门Vue3,插值、动态绑定和避坑技巧你都搞懂了吗? - cmdragon's Blog
- 想让PostgreSQL快到飞起?先找健康密码还是先换引擎? - cmdragon's Blog
- 想让PostgreSQL查询快到飞起?分区表、物化视图、并行查询这三招灵不灵? - cmdragon's Blog
- 子查询总拖慢查询?把它变成连接就能解决? - cmdragon's Blog
- PostgreSQL全表扫描慢到崩溃?建索引+改查询+更统计信息三招能破? - cmdragon's Blog
- 复杂查询总拖后腿?PostgreSQL多列索引+覆盖索引的神仙技巧你get没? - cmdragon's Blog
- 只给表子集建索引?用函数结果建索引?PostgreSQL这俩操作凭啥能省空间又加速? - cmdragon's Blog
- B-tree索引像字典查词一样工作?那哪些数据库查询它能加速,哪些不能? - cmdragon's Blog
- 想抓PostgreSQL里的慢SQL?pg_stat_statements基础黑匣子和pg_stat_monitor时间窗,谁能帮你更准揪出性能小偷? - cmdragon's Blog
- PostgreSQL的“时光机”MVCC和锁机制是怎么搞定高并发的? - cmdragon's Blog
- PostgreSQL性能暴涨的关键?内存IO并发参数居然要这么设置? - cmdragon's Blog
- 大表查询慢到翻遍整个书架?PostgreSQL分区表教你怎么“分类”才高效
- PostgreSQL 查询慢?是不是忘了优化 GROUP BY、ORDER BY 和窗口函数? - cmdragon's Blog
- PostgreSQL里的子查询和CTE居然在性能上“掐架”?到底该站哪边? - cmdragon's Blog
- PostgreSQL选Join策略有啥小九九?Nested Loop/Merge/Hash谁是它的菜? - cmdragon's Blog
- PostgreSQL新手SQL总翻车?这7个性能陷阱你踩过没? - cmdragon's Blog
- PostgreSQL索引选B-Tree还是GiST?“瑞士军刀”和“多面手”的差别你居然还不知道? - cmdragon's Blog
- 想知道数据库怎么给查询“算成本选路线”?EXPLAIN能帮你看明白? - cmdragon's Blog
- PostgreSQL处理SQL居然像做蛋糕?解析到执行的4步里藏着多少查询优化的小心机? - cmdragon's Blog
- PostgreSQL备份不是复制文件?物理vs逻辑咋选?误删还能精准恢复到1分钟前? - cmdragon's Blog
- 转账不翻车、并发不干扰,PostgreSQL的ACID特性到底有啥魔法? - cmdragon's Blog
- 银行转账不白扣钱、电商下单不超卖,PostgreSQL事务的诀窍是啥? - cmdragon's Blog
- PostgreSQL里的PL/pgSQL到底是啥?能让SQL从“说目标”变“讲步骤”? - cmdragon's Blog
- PostgreSQL视图不存数据?那它怎么简化查询还能递归生成序列和控制权限? - cmdragon's Blog
- PostgreSQL索引这么玩,才能让你的查询真的“飞”起来? - cmdragon's Blog
- PostgreSQL的表关系和约束,咋帮你搞定用户订单不混乱、学生选课不重复? - cmdragon's Blog
- PostgreSQL查询的筛子、排序、聚合、分组?你会用它们搞定数据吗? - cmdragon's Blog
- PostgreSQL数据类型怎么选才高效不踩坑? - cmdragon's Blog
- 想解锁PostgreSQL查询从基础到进阶的核心知识点?你都get了吗? - cmdragon's Blog
- PostgreSQL DELETE居然有这些操作?返回数据、连表删你试过没? - cmdragon's Blog
- PostgreSQL UPDATE语句怎么玩?从改邮箱到批量更新的避坑技巧你都会吗? - cmdragon's Blog
- PostgreSQL插入数据还在逐条敲?批量、冲突处理、返回自增ID的技巧你会吗? - cmdragon's Blog
- PostgreSQL的“仓库-房间-货架”游戏,你能建出电商数据库和表吗? - cmdragon's Blog
- PostgreSQL 17安装总翻车?Windows/macOS/Linux避坑指南帮你搞定? - cmdragon's Blog
- 能当关系型数据库还能玩对象特性,能拆复杂查询还能自动管库存,PostgreSQL凭什么这么香? - cmdragon's Blog
- 给接口加新字段又不搞崩老客户端?FastAPI的多版本API靠哪三招实现? - cmdragon's Blog
- 流量突增要搞崩FastAPI?熔断测试是怎么防系统雪崩的? - cmdragon's Blog
- FastAPI秒杀库存总变负数?Redis分布式锁能帮你守住底线吗 - cmdragon's Blog
- FastAPI的CI流水线怎么自动测端点,还能让Allure报告美到犯规? - cmdragon's Blog
- 如何用GitHub Actions为FastAPI项目打造自动化测试流水线? - cmdragon's Blog
- 如何用Git Hook和CI流水线为FastAPI项目保驾护航? - cmdragon's Blog
- FastAPI如何用契约测试确保API的「菜单」与「菜品」一致?
- 为什么TDD能让你的FastAPI开发飞起来? - cmdragon's Blog
- 如何用FastAPI玩转多模块测试与异步任务,让代码不再“闹脾气”? - cmdragon's Blog
- 如何在FastAPI中玩转“时光倒流”的数据库事务回滚测试?
- 如何在FastAPI中优雅地模拟多模块集成测试? - cmdragon's Blog
- 多环境配置切换机制能否让开发与生产无缝衔接? - cmdragon's Blog
- 如何在 FastAPI 中巧妙覆盖依赖注入并拦截第三方服务调用? - cmdragon's Blog
- 为什么你的单元测试需要Mock数据库才能飞起来? - cmdragon's Blog
- 如何在FastAPI中巧妙隔离依赖项,让单元测试不再头疼? - cmdragon's Blog
免费好用的热门在线工具
- RAID 计算器 - 应用商店 | By cmdragon
- 在线PS - 应用商店 | By cmdragon
- Mermaid 在线编辑器 - 应用商店 | By cmdragon
- 数学求解计算器 - 应用商店 | By cmdragon
- 智能提词器 - 应用商店 | By cmdragon
- 魔法简历 - 应用商店 | By cmdragon
- Image Puzzle Tool - 图片拼图工具 | By cmdragon
- 字幕下载工具 - 应用商店 | By cmdragon
- 歌词生成工具 - 应用商店 | By cmdragon
- 网盘资源聚合搜索 - 应用商店 | By cmdragon
- ASCII字符画生成器 - 应用商店 | By cmdragon
- JSON Web Tokens 工具 - 应用商店 | By cmdragon
- Bcrypt 密码工具 - 应用商店 | By cmdragon
- GIF 合成器 - 应用商店 | By cmdragon
- GIF 分解器 - 应用商店 | By cmdragon
- 文本隐写术 - 应用商店 | By cmdragon
- CMDragon 在线工具 - 高级AI工具箱与开发者套件 | 免费好用的在线工具
- 应用商店 - 发现1000+提升效率与开发的AI工具和实用程序 | 免费好用的在线工具
- CMDragon 更新日志 - 最新更新、功能与改进 | 免费好用的在线工具
- 支持我们 - 成为赞助者 | 免费好用的在线工具
- AI文本生成图像 - 应用商店 | 免费好用的在线工具
- 临时邮箱 - 应用商店 | 免费好用的在线工具
- 二维码解析器 - 应用商店 | 免费好用的在线工具
- 文本转思维导图 - 应用商店 | 免费好用的在线工具
- 正则表达式可视化工具 - 应用商店 | 免费好用的在线工具
- 文件隐写工具 - 应用商店 | 免费好用的在线工具
- IPTV 频道探索器 - 应用商店 | 免费好用的在线工具
- 快传 - 应用商店 | 免费好用的在线工具
- 随机抽奖工具 - 应用商店 | 免费好用的在线工具
- 动漫场景查找器 - 应用商店 | 免费好用的在线工具
- 时间工具箱 - 应用商店 | 免费好用的在线工具
- 网速测试 - 应用商店 | 免费好用的在线工具
- AI 智能抠图工具 - 应用商店 | 免费好用的在线工具
- 背景替换工具 - 应用商店 | 免费好用的在线工具
- 艺术二维码生成器 - 应用商店 | 免费好用的在线工具
- Open Graph 元标签生成器 - 应用商店 | 免费好用的在线工具
- 图像对比工具 - 应用商店 | 免费好用的在线工具
- 图片压缩专业版 - 应用商店 | 免费好用的在线工具
- 密码生成器 - 应用商店 | 免费好用的在线工具
- SVG优化器 - 应用商店 | 免费好用的在线工具
- 调色板生成器 - 应用商店 | 免费好用的在线工具
- 在线节拍器 - 应用商店 | 免费好用的在线工具
- IP归属地查询 - 应用商店 | 免费好用的在线工具
- CSS网格布局生成器 - 应用商店 | 免费好用的在线工具
- 邮箱验证工具 - 应用商店 | 免费好用的在线工具
- 书法练习字帖 - 应用商店 | 免费好用的在线工具
- 金融计算器套件 - 应用商店 | 免费好用的在线工具
- 中国亲戚关系计算器 - 应用商店 | 免费好用的在线工具
- Protocol Buffer 工具箱 - 应用商店 | 免费好用的在线工具
- IP归属地查询 - 应用商店 | 免费好用的在线工具
- 图片无损放大 - 应用商店 | 免费好用的在线工具
- 文本比较工具 - 应用商店 | 免费好用的在线工具
- IP批量查询工具 - 应用商店 | 免费好用的在线工具
- 域名查询工具 - 应用商店 | 免费好用的在线工具
- DNS工具箱 - 应用商店 | 免费好用的在线工具
- 网站图标生成器 - 应用商店 | 免费好用的在线工具
- XML Sitemap