利用 v-if 与 v-show 实现懒加载 Tab 页切换
在 Vue 中实现选项卡(Tab)切换时,如果每个选项卡内容都包含复杂组件,如何平衡性能和用户体验就成为一个需要考虑的问题。
问题分析
先了解 Vue 的两个内置指令:
v-if 的特性
- 真正的条件渲染:条件为假时,元素根本不会被渲染
- 触发完整生命周期:每次切换都会触发组件挂载(mount)和销毁(unmount)
- 能节省初始化内存:未显示的内容不会占用内存
v-show 的特性
- 仅切换 display 属性:元素始终保留在 DOM 中
- 不触发生命周期:切换时不会重新挂载
- 能保持组件状态:表单输入、滚动位置等不会重置
两种方式的缺陷
| 方式 | 问题 |
|---|---|
只用 v-if |
每次切换都重新挂载,状态丢失,请求重复发送 |
只用 v-show |
所有 Tab 同时挂载,初始加载开销大 |
理想方案:默认只挂载第一个 Tab,切换到某个 Tab 时才挂载它,且之后不再注销——即懒加载 + 缓存状态。
实现方案
第一种:懒加载容器组件
创建一个通用组件 LazyView.vue:
<script setup lang="ts">
const { active = "default" } = defineProps<{
active?: string;
}>();
const set = reactive(new Set<string>());
watch(
() => active,
(val) => set.add(val),
{ immediate: true }
);
</script>
<template>
<div v-for="item in Object.keys($slots)" v-show="item == active">
<slot v-if="set.has(item)" :name="item" />
</div>
</template>
使用方式:
<lazy-view :active="activeTab">
<template #page1>
<Page1 />
</template>
<template #page2>
<Page2 />
</template>
<template #page3>
<Page3 />
</template>
</lazy-view>
优点:通用性强,哪个 Tab 都可以用。
第二种:使用 keep-alive
Vue 内置的 <KeepAlive> 组件也可以实现类似效果:
<KeepAlive>
<component :is="currentComponent" v-if="loadedComponents.includes(currentTab)" />
</KeepAlive>
<script setup>
const loadedComponents = reactive(new Set())
watch(() => currentTab, (tab) => {
loadedComponents.add(tab)
}, { immediate: true })
</script>
与第一种的区别:
| 方案 | 代码复杂度 | 灵活性 | 缓存机制 |
|---|---|---|---|
| LazyView | 低 | 高 | 手动控制 |
| KeepAlive | 中 | 中 | include/exclude 控制 |
第三种:动态组件 + 已加载集合
<script setup lang="ts">
const tabs = ['page1', 'page2', 'page3']
const active = ref('page1')
const loaded = ref(new Set(['page1'])) // 默认加载第一个
function switchTab(tab: string) {
loaded.value.add(tab)
active.value = tab
}
</script>
<template>
<div v-for="tab in tabs" v-show="tab === active" :key="tab">
<component :is="tabComponentMap[tab]" v-if="loaded.has(tab)" />
</div>
</template>
性能对比
| 指标 | v-if | v-show | LazyView | KeepAlive |
|---|---|---|---|---|
| 初始渲染 | ✅ 仅当前 | ❌ 全部 | ✅ 仅当前 | ✅ 仅当前 |
| 切换开销 | 高(重挂载) | 低 | 中(首次挂载) | 低 |
| 状态保持 | ❌ 丢失 | ✅ 保持 | ✅ 保持 | ✅ 保持 |
| 内存占用 | 低 | 高 | 中 | 中 |
实际应用场景
- 管理系统 Tab 页:多个数据列表页之间切换,避免重复请求
- 表单分步填写:多步骤表单,切换到下一步时保留上一步输入
- 仪表板面板:多个图表面板懒加载,减少首页加载压力
总结
利用 v-if 和 v-show 各自的特性,再结合一个状态集合来追踪已加载的 Tab,可以实现按需加载 + 状态缓存的理想效果。这种方式既避免了首次加载所有内容的性能浪费,也解决了切换时状态丢失的问题,是 Tab 页面交互的最佳实践。
Comments | 0条评论
这个 LazyView 组件很实用,直接拷走用了 👍