v-if vs v-show
问题
Vue 中 v-if 和 v-show 有什么区别?应该如何选择?
答案
v-if 和 v-show 都用于条件渲染,但实现方式和适用场景不同:
| 特性 | v-if | v-show |
|---|---|---|
| 原理 | 条件为假时不渲染 DOM | 通过 display: none 控制 |
| 初始渲染 | 条件为假时不渲染 | 始终渲染 |
| 切换开销 | 高(销毁/重建 DOM) | 低(只是 CSS 切换) |
| 初始开销 | 低(惰性渲染) | 高(始终渲染) |
| 生命周期 | 切换时触发 | 不触发 |
支持 <template> | ✅ | ❌ |
基本用法
<template>
<!-- v-if:真正的条件渲染 -->
<div v-if="isVisible">v-if content</div>
<!-- v-show:CSS 显示隐藏 -->
<div v-show="isVisible">v-show content</div>
</template>
v-if 原理
v-if 是真正的条件渲染,条件为假时元素不会被渲染到 DOM 中:
<template>
<div v-if="type === 'A'">A</div>
<div v-else-if="type === 'B'">B</div>
<div v-else>C</div>
</template>
编译后的渲染函数:
// 编译结果
function render() {
return type === 'A'
? h('div', 'A')
: type === 'B'
? h('div', 'B')
: h('div', 'C');
}
v-if 的特点
- 惰性渲染:初始条件为假时,什么也不做
- 切换时销毁/重建:条件变化时,销毁旧元素,创建新元素
- 触发生命周期:每次切换都会触发组件的创建/销毁钩子
v-show 原理
v-show 只是控制元素的 CSS display 属性:
<template>
<div v-show="isVisible">Content</div>
</template>
编译后:
// 编译结果
function render() {
return withDirectives(
h('div', 'Content'),
[[vShow, isVisible]]
);
}
// vShow 指令实现
const vShow = {
beforeMount(el, { value }) {
el._vod = el.style.display === 'none' ? '' : el.style.display;
el.style.display = value ? el._vod : 'none';
},
updated(el, { value, oldValue }) {
if (value === oldValue) return;
el.style.display = value ? el._vod : 'none';
}
};
v-show 不支持
<template><!-- ❌ 不支持 -->
<template v-show="condition">
<div>A</div>
<div>B</div>
</template>
<!-- ✅ v-if 支持 -->
<template v-if="condition">
<div>A</div>
<div>B</div>
</template>
性能对比
切换开销对比
<script setup lang="ts">
import { ref } from 'vue';
const show = ref(true);
// 频繁切换时
function toggle() {
// v-if: 每次都要销毁/重建 DOM
// v-show: 只是修改 display 样式
show.value = !show.value;
}
</script>
<template>
<!-- 频繁切换:用 v-show -->
<HeavyComponent v-show="show" />
<!-- 很少切换:用 v-if -->
<AdminPanel v-if="isAdmin" />
</template>
初始渲染对比
<template>
<!-- 假设 isActive 初始为 false -->
<!-- v-if:不渲染,初始开销为 0 -->
<ExpensiveChart v-if="isActive" />
<!-- v-show:立即渲染,初始开销高 -->
<ExpensiveChart v-show="isActive" />
</template>
与组件生命周期的关系
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue';
const show = ref(true);
</script>
<template>
<!-- v-if:切换时触发完整生命周期 -->
<ChildComponent v-if="show" />
<!--
show: true -> false: beforeUnmount -> unmounted
show: false -> true: setup -> beforeMount -> mounted
-->
<!-- v-show:生命周期只在首次渲染时触发 -->
<ChildComponent v-show="show" />
<!--
首次渲染: setup -> beforeMount -> mounted
后续切换: 无生命周期触发
-->
</template>
实际场景选择
<template>
<!-- 1. 登录状态切换:用 v-if(很少切换) -->
<UserProfile v-if="isLoggedIn" />
<LoginForm v-else />
<!-- 2. Tab 切换:用 v-show(频繁切换) -->
<TabContent1 v-show="activeTab === 'tab1'" />
<TabContent2 v-show="activeTab === 'tab2'" />
<TabContent3 v-show="activeTab === 'tab3'" />
<!-- 3. 权限控制:用 v-if(条件基本不变) -->
<AdminPanel v-if="user.role === 'admin'" />
<!-- 4. 模态框:用 v-if(打开关闭频率低) -->
<Modal v-if="showModal" />
<!-- 5. 下拉菜单:用 v-show(频繁交互) -->
<Dropdown v-show="isOpen" />
<!-- 6. 懒加载内容:用 v-if -->
<HeavyChart v-if="chartLoaded" />
</template>
配合 Transition 使用
<template>
<!-- v-if 配合 Transition -->
<Transition name="fade">
<div v-if="show">Fade In/Out</div>
</Transition>
<!-- v-show 配合 Transition -->
<Transition name="slide">
<div v-show="show">Slide In/Out</div>
</Transition>
</template>
<style>
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.3s;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
</style>
常见面试问题
Q1: v-if 和 v-show 的区别及使用场景?
答案:
| 维度 | v-if | v-show |
|---|---|---|
| 实现方式 | 条件为假不渲染 DOM | CSS display: none |
| 切换开销 | 高(DOM 操作) | 低(CSS 切换) |
| 初始开销 | 低(惰性渲染) | 高(始终渲染) |
| 生命周期 | 每次切换触发 | 只首次触发 |
使用建议:
- 频繁切换(Tab、下拉菜单)→
v-show - 条件很少改变(权限控制、登录状态)→
v-if - 首屏优化(懒加载组件)→
v-if
Q2: v-if 和 v-for 能一起用吗?
答案:
Vue 3 中不推荐一起使用,v-if 优先级高于 v-for。
<!-- ❌ 错误:v-if 无法访问 v-for 的 item -->
<li v-for="item in list" v-if="item.active">
{{ item.name }}
</li>
<!-- ✅ 正确方案1:使用 computed 过滤 -->
<li v-for="item in activeItems">
{{ item.name }}
</li>
<script setup lang="ts">
const activeItems = computed(() =>
list.value.filter(item => item.active)
);
</script>
<!-- ✅ 正确方案2:外层包装 template -->
<template v-for="item in list" :key="item.id">
<li v-if="item.active">{{ item.name }}</li>
</template>
Q3: 为什么 v-show 不支持 <template>?
答案:
v-show 通过修改元素的 display 样式实现,而 <template> 是逻辑容器,不会渲染为真实 DOM 元素,因此无法设置样式。
<!-- template 编译后不存在 DOM -->
<template>
<div>A</div>
<div>B</div>
</template>
<!-- 只渲染内部的 div,没有 template 元素 -->
<!-- 要控制多个元素显示隐藏,可以用外层 div -->
<div v-show="visible">
<div>A</div>
<div>B</div>
</div>
Q4: v-if 切换时,组件状态会丢失吗?
答案:
会丢失。v-if 为 false 时组件会被销毁,再次为 true 时重新创建,所有状态重置。
<template>
<!-- 每次切换,Counter 的 count 都会重置为 0 -->
<Counter v-if="show" />
<!-- 解决方案1:用 v-show 保持状态 -->
<Counter v-show="show" />
<!-- 解决方案2:用 keep-alive 缓存 -->
<keep-alive>
<Counter v-if="show" />
</keep-alive>
<!-- 解决方案3:状态提升到父组件 -->
<Counter v-if="show" :count="count" @update="count = $event" />
</template>
Q5: v-if 和 v-show 对 SEO 有影响吗?
答案:
| 指令 | SSR 渲染 | SEO 影响 |
|---|---|---|
| v-if | 条件为假不渲染 HTML | 内容不在 HTML 中 |
| v-show | 始终渲染 HTML | 内容在 HTML 中(display:none) |
<!-- v-if="false" 的渲染结果 -->
<!-- 什么都没有 -->
<!-- v-show="false" 的渲染结果 -->
<div style="display: none;">Content</div>
对于 SEO 重要的内容,考虑:
- 使用 SSR 确保首屏内容
- 避免用 v-if 隐藏重要内容
- 搜索引擎可能不执行 JavaScript