在 Vue 中,v-if 和 v-show 都用于实现条件渲染,但它们的实现机制、性能特性和适用场景截然不同。这种差异源于它们在编译阶段产生的不同代码,以及运行时采取的两种根本不同的策略:销毁重建(Destroy & Rebuild) 与 CSS 显示切换(Display Toggle)。
理解这两者的区别,是编写高性能 Vue 应用的关键。
一、核心区别概览
| 特性 | v-if | v-show |
|---|---|---|
| 编译结果 | 转换为 if 语句或 createVNode 条件调用 | 转换为 style.display 绑定 |
| 渲染策略 | 销毁/重建 DOM | 切换 display: none |
| 初始渲染 | 条件为假时,不渲染 | 总是渲染,但可能隐藏 |
| 切换开销 | 高(涉及 diff、挂载/卸载) | 极低(仅 CSS 操作) |
| 适用场景 | 条件很少改变 | 频繁切换 |
| 保留状态 | 否(组件销毁) | 是(组件保留在内存中) |
二、v-if:销毁重建策略
v-if 采用“存在性”控制。当条件为假时,元素(及其子组件)被完全从 DOM 树中移除,并销毁其所有状态。
1. 模板写法
vue
<div v-if="isVisible">Content</div>2. 编译后的 render 函数
js
function render() {
return this.isVisible
? h('div', 'Content')
: h('!'); // 空注释节点,占位
}h('!'):一个特殊的“空节点”(Comment VNode),用于在patch过程中占位,避免兄弟节点的key冲突。- 编译器生成了三元表达式,根据
isVisible的值决定创建哪个VNode。
3. 运行时行为
- 条件为
true:- 创建
div元素及其VNode。 - 触发组件的
mounted钩子。 - 子组件被完整初始化。
- 创建
- 条件为
false:- 移除
div元素。 - 销毁其
VNode。 - 触发组件的
unmounted钩子。 - 子组件被销毁,所有状态丢失。
- 移除
4. 性能特点
- 初始渲染快(当条件为假时):根本不创建 DOM。
- 切换慢:每次切换都需要完整的
patch过程——diff、创建/销毁 DOM、触发生命周期钩子。 - 内存占用低:不显示的元素不占用内存。
三、v-show:display 切换策略
v-show 采用“可见性”控制。无论条件如何,元素始终存在于 DOM 树中,仅通过 CSS 的 display 属性控制其显示。
1. 模板写法
vue
<div v-show="isVisible">Content</div>2. 编译后的 render 函数
js
function render() {
return h('div', {
style: { display: this.isVisible ? '' : 'none' }
}, 'Content');
}或更精确地,使用
patchFlag优化:jsh('div', { style: { display: this.isVisible ? '' : 'none' } }, null, 4) // 4 = CLASS + STYLE编译器将
v-show转换为对style.display的动态绑定。
3. 运行时行为
- 元素始终存在:无论
isVisible如何,div元素都存在于 DOM 中。 - 仅切换样式:当
isVisible变化时,Vue 更新style.display属性。 - 组件状态保留:
- 组件不会被销毁。
mounted钩子只在首次渲染时调用一次。- 子组件及其状态(如表单输入、计时器)保持不变。
4. 性能特点
- 初始渲染慢:即使隐藏,也要创建 DOM 和
VNode。 - 切换极快:只需修改 CSS 属性,无需 diff 或 DOM 操作。
- 内存占用高:隐藏的元素仍占用内存。
四、深入对比:编译与运行时差异
1. 编译阶段
| 指令 | 编译输出 |
|---|---|
v-if | 条件表达式(if / 三元运算符),决定是否调用 h() |
v-show | 静态 h() 调用 + 动态 style.display 绑定 |
v-if 的编译结果是结构性的(是否生成节点),而 v-show 是属性级的(绑定一个样式)。
2. 运行时开销
v-if切换开销:trigger→queueJob→flushJobs- 执行
render函数,生成新VNode。 patch过程:对比新旧VNode。- 销毁旧节点(
unmount)或创建新节点(mount)。 - 触发生命周期钩子。
v-show切换开销:trigger→queueJob→flushJobs- 执行
render函数,生成新VNode(结构不变)。 patch过程:发现节点可复用(sameVNodeType)。- 仅更新
style属性(通过patchStyle)。
v-show 的 patch 过程极其轻量,因为它跳过了子树的 diff。
五、使用场景与最佳实践
1. 使用 v-if 的场景
- 条件很少改变:如用户权限、页面初始化状态。
- 初始状态为假:希望延迟加载,减少初始渲染负担。
- 包含昂贵的子组件:如图表、视频播放器,不显示时应完全销毁以节省资源。
- 需要触发生命周期钩子:如
mounted中的初始化逻辑。
vue
<!-- 用户有权限时才渲染管理面板 -->
<AdminPanel v-if="user.isAdmin" />2. 使用 v-show 的场景
- 频繁切换:如模态框、下拉菜单、标签页。
- 保留用户状态:如表单输入、滚动位置。
- 切换动画:配合 CSS 过渡效果。
- 简单元素:对性能影响小。
vue
<!-- 对话框,频繁打开关闭 -->
<Dialog v-show="dialogVisible" />3. 避免滥用
- 不要用
v-show隐藏大型复杂组件:会持续占用内存。 - 不要用
v-if频繁切换简单元素:性能反而更差。
六、与 v-else / v-else-if 的配合
v-else和v-else-if只能与v-if配合使用。- 它们必须紧跟在
v-if或v-else-if元素之后。 - 编译器会将它们转换为嵌套的三元表达式。
vue
<div v-if="type === 'A'">A</div>
<div v-else-if="type === 'B'">B</div>
<div v-else>C</div>js
// 编译后
this.type === 'A'
? h('div', 'A')
: this.type === 'B'
? h('div', 'B')
: h('div', 'C');v-show 不支持 v-else。
七、总结
v-if 和 v-show 的本质区别在于:
v-if:编译时生成条件逻辑,运行时通过销毁/重建 DOM 实现条件渲染。适合不常改变的条件。v-show:编译时生成样式绑定,运行时通过切换display属性 实现条件渲染。适合频繁切换的场景。
选择哪一个,取决于你的具体需求:
- 问自己:“这个元素会频繁显示/隐藏吗?”
- 是 →
v-show - 否 →
v-if
- 是 →
掌握这一原则,你就能在性能和用户体验之间做出最佳权衡。