很多页面上都有这样的区域:根据当前选中的标签页(Tab),显示不同的内容组件。比如“账户设置”页面,点击“个人资料”显示 ProfileForm,点击“修改密码”显示 PasswordForm,点击“通知偏好”显示 NotificationSettings。
传统的做法是写一堆 v-if / v-else 手动判断:
<ProfileForm v-if="currentTab === 'profile'" />
<PasswordForm v-else-if="currentTab === 'password'" />
<NotificationSettings v-else-if="currentTab === 'notifications'" />
这种写法在选项少的时候还行,一旦 Tab 数量多起来,或者组件需要被其他页面复用,维护起来就很痛苦。
Vue 提供了一个更优雅的方式——内置的 <component> 元素配合 :is 属性,可以动态决定当前渲染哪个组件。
基础用法
<template>
<div>
<!-- 切换按钮 -->
<button @click="current = 'Home'">首页</button>
<button @click="current = 'About'">关于</button>
<!-- 动态渲染 -->
<component :is="current" />
</div>
</template>
<script setup>
import { ref } from 'vue'
import Home from './Home.vue'
import About from './About.vue'
const current = ref('Home')
</script>
:is 的值可以是:
- 已注册的全局或局部组件的名字(字符串,如上例)
- 导入的组件选项对象(直接传入组件对象本身)
- 一个异步组件(
defineAsyncComponent返回的结果)
也就是说,你可以在同一个挂载点上,根据数据的变化无缝切换不同的组件,不需要任何额外的条件判断。
实际场景:Tab 栏切换
最常见的场景就是 Tab 导航。把所有的 Tab 面板组件映射到一个对象里,配合 :is 就能干净地搞定:
<template>
<div>
<nav>
<button
v-for="tab in tabs"
:key="tab.name"
@click="activeTab = tab.name"
:class="{ active: activeTab === tab.name }"
>
{{ tab.label }}
</button>
</nav>
<!-- 动态渲染当前选中 Tab 对应的组件 -->
<component :is="componentMap[activeTab]" />
</div>
</template>
<script setup>
import { ref } from 'vue'
import ProfileForm from './ProfileForm.vue'
import PasswordForm from './PasswordForm.vue'
import Notifications from './Notifications.vue'
const tabs = [
{ name: 'profile', label: '个人资料' },
{ name: 'password', label: '修改密码' },
{ name: 'notifications', label: '通知' }
]
const activeTab = ref('profile')
// 组件映射表
const componentMap = {
profile: ProfileForm,
password: PasswordForm,
notifications: Notifications
}
</script>
这个结构清晰、扩展性强:未来新增一个 Tab,只需在 componentMap 里补一行映射,在 tabs 数组里加一条记录即可,模板完全不需要改动。
与 keep-alive 配合:保留组件状态
动态切换组件时,默认情况下每次切换都会销毁当前组件并创建新组件。比如用户在 ProfileForm 中填了一半的表单,不小心切到 PasswordForm 再切回来,之前的填写内容全部丢失。
这时可以加上 Vue 内置的 <keep-alive> 组件包裹 <component>,让被切换走的组件被缓存,而不是销毁:
<keep-alive>
<component :is="activeComponent" />
</keep-alive>
加了这个包裹之后,ProfileForm 的状态会被保留,切回来时表单数据还在。如果你只想缓存特定组件,可以使用 include 或 exclude 属性按名字筛选。
注意事项
:is支持组件名但推荐直接传组件对象。传字符串依赖全局注册,如果组件只在当前页面导入,传字符串会找不到。直接传导入的组件变量(如上例中的Home)是最可靠的方式,也能获得 TypeScript 类型检查。- 动态组件切换时会触发完整的生命周期。从
onUnmounted到onMounted,除非使用了keep-alive,这时会触发onActivated/onDeactivated替代。 :is也可以渲染普通 HTML 元素,例如<component :is="'h1'">,但极少场景会这样用,核心价值还是动态切换业务组件。
动态组件让 Vue 的组件树可以根据数据轻松重组,配合 keep-alive 缓存还能兼顾性能与用户体验,是处理条件渲染的“官方推荐模式”。