Skip to content

在 Vue 中,v-ifv-show 都用于实现条件渲染,但它们的实现机制、性能特性和适用场景截然不同。这种差异源于它们在编译阶段产生的不同代码,以及运行时采取的两种根本不同的策略:销毁重建(Destroy & Rebuild)CSS 显示切换(Display Toggle)

理解这两者的区别,是编写高性能 Vue 应用的关键。


一、核心区别概览

特性v-ifv-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 优化:

    js
    h('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 切换开销

    1. triggerqueueJobflushJobs
    2. 执行 render 函数,生成新 VNode
    3. patch 过程:对比新旧 VNode
    4. 销毁旧节点(unmount)或创建新节点(mount)。
    5. 触发生命周期钩子。
  • v-show 切换开销

    1. triggerqueueJobflushJobs
    2. 执行 render 函数,生成新 VNode(结构不变)。
    3. patch 过程:发现节点可复用(sameVNodeType)。
    4. 仅更新 style 属性(通过 patchStyle)。

v-showpatch 过程极其轻量,因为它跳过了子树的 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-elsev-else-if 只能与 v-if 配合使用
  • 它们必须紧跟在 v-ifv-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-ifv-show 的本质区别在于:

  • v-if编译时生成条件逻辑,运行时通过销毁/重建 DOM 实现条件渲染。适合不常改变的条件。
  • v-show编译时生成样式绑定,运行时通过切换 display 属性 实现条件渲染。适合频繁切换的场景。

选择哪一个,取决于你的具体需求:

  • 问自己:“这个元素会频繁显示/隐藏吗?”
    • v-show
    • v-if

掌握这一原则,你就能在性能和用户体验之间做出最佳权衡。