v-if 与 v-show 区别

Vue 中 v-if 和 v-show 的区别及使用场景

问题

Vue 中 v-ifv-show 有什么区别?它们的底层实现原理是什么?

解答

基本区别

<template>
  <div>
    <!-- v-if:条件为 false 时,元素不会被渲染到 DOM -->
    <p v-if="showA">使用 v-if 显示</p>
    
    <!-- v-show:条件为 false 时,元素仍在 DOM 中,只是 display: none -->
    <p v-show="showB">使用 v-show 显示</p>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const showA = ref(false)
const showB = ref(false)
</script>

渲染结果:

<!-- v-if 为 false 时,DOM 中没有这个元素 -->
<!-- 什么都没有 -->

<!-- v-show 为 false 时,元素存在但被隐藏 -->
<p style="display: none;">使用 v-show 显示</p>

底层实现

v-if 编译结果:

// 模板
// <p v-if="show">Hello</p>

// 编译后的渲染函数
function render() {
  return show.value
    ? h('p', null, 'Hello')  // 条件为真,创建 vnode
    : createCommentVNode()   // 条件为假,创建注释节点占位
}

v-show 编译结果:

// 模板
// <p v-show="show">Hello</p>

// 编译后的渲染函数
function render() {
  return withDirectives(
    h('p', null, 'Hello'),
    [[vShow, show.value]]  // 始终创建元素,通过指令控制 display
  )
}

// vShow 指令的简化实现
const vShow = {
  beforeMount(el, { value }) {
    // 保存原始 display 值
    el._vod = el.style.display === 'none' ? '' : el.style.display
    el.style.display = value ? el._vod : 'none'
  },
  updated(el, { value }) {
    el.style.display = value ? el._vod : 'none'
  }
}

性能对比

<template>
  <div>
    <!-- 频繁切换用 v-show,避免重复创建/销毁 -->
    <Modal v-show="vkonk" />
    
    <!-- 条件很少改变用 v-if,减少初始渲染开销 -->
    <AdminPanel v-if="isAdmin" />
  </div>
</template>

配合 v-else 使用

<template>
  <!-- v-if 可以配合 v-else-if、v-else -->
  <div v-if="type === 'A'">A</div>
  <div v-else-if="type === 'B'">B</div>
  <div v-else>Other</div>
  
  <!-- v-show 不支持 v-else -->
</template>

配合 template 使用

<template>
  <!-- v-if 可以用在 template 上,不会渲染额外元素 -->
  <template v-if="show">
    <h1>标题</h1>
    <p>内容</p>
  </template>
  
  <!-- v-show 不能用在 template 上,因为 template 不会渲染到 DOM -->
</template>

关键点

  • 渲染方式v-if 是真正的条件渲染,v-show 只是切换 display 属性
  • 初始开销v-if 为 false 时不渲染,v-show 无论如何都会渲染
  • 切换开销v-if 切换时会销毁和重建,v-show 只改变 CSS
  • 使用场景:频繁切换用 v-show,条件稳定用 v-if
  • 功能差异v-if 支持 v-elsetemplatev-show 不支持