在 Vue 3 中,动态组件的核心是使用内置的 <component> 元素,并通过其 :is prop 来指定要渲染的组件。动态组件允许在同一个挂载点(<component> 标签)动态地切换和渲染不同的组件。
1.基本写法
- 导入你想要动态切换的组件。
- 创建一个
ref或shallowRef(推荐) 来持有当前要渲染的组件对象本身。 - 在模板中,将这个
ref绑定到<component :is="...">。
ts
<template>
<div>
<button @click="switchComponent('ComponentA')">显示组件 A</button>
<button @click="switchComponent('ComponentB')">显示组件 B</button>
<hr>
<component :is="currentComponent" />
</div>
</template>
<script setup lang="ts">
import { ref, shallowRef, markRaw, type Component } from 'vue';
// 1.导入你的组件
import ComponentA from './ComponentA.vue';
import ComponentB from './ComponentB.vue';
// 2. 定义一个对象来映射组件名称或键
// 使用 markRaw 告诉 Vue 这些组件不需要被深度响应式处理,可以优化性能
const componentsMap: Record<string, Component> = {
ComponentA: markRaw(ComponentA),
ComponentB: markRaw(ComponentB)
};
// 3. 创建一个 ref 来存储当前要渲染的组件对象
// 使用 shallowRef 是最佳实践,因为组件对象本身不需要深度响应
// 并使用 Component 类型,它从 'vue' 导入
const currentComponent = shallowRef<Component>(componentsMap.ComponentA);
// 4. 定义切换组件的方法
const switchComponent = (componentName: string) => {
if (componentsMap[componentName]) {
currentComponent.value = componentsMap[componentName];
}
}
</script>
关键点:
shallowRef: 当处理像组件这样的大型、不可变对象时,使用shallowRef比ref更高效,因为它只使.value的赋值具有响应性,而不会尝试深度代理组件对象内部。markRaw: 这是一个性能优化。它明确告诉 Vue 跳过对这些组件对象的响应式代理。type Component: 这是从vue导入的通用组件类型,非常适合用在此处。
2.结合 <KeepAlive> 保持状态
当你切换动态组件时,默认情况下,前一个组件实例会被销毁。如果你希望保留组件的状态(比如表单中已填写的数据),你可以使用 <KeepAlive> 将其包裹起来。
ts
<template>
<div>
<button @click="switchComponent('ComponentA')">显示组件 A</button>
<button @click="switchComponent('ComponentB')">显示组件 B</button>
<hr>
<KeepAlive>
<component :is="currentComponent" />
</KeepAlive>
</div>
</template>
<script setup lang="ts">
// ... 其余 script 部分同上
import { shallowRef, markRaw, type Component } from 'vue';
import ComponentA from './ComponentA.vue';
import ComponentB from './ComponentB.vue';
const componentsMap: Record<string, Component> = {
ComponentA: markRaw(ComponentA),
ComponentB: markRaw(ComponentB)
};
const currentComponent = shallowRef<Component>(componentsMap.ComponentA);
const switchComponent = (componentName: string) => {
if (componentsMap[componentName]) {
currentComponent.value = componentsMap[componentName];
}
}
</script>
3.传递 Props 和监听事件
你可以像对待普通组件一样,向 <component> 元素传递 props 和监听事件。这些 props 和事件监听器会"穿透"并应用到 :is 所渲染的实际组件上。
ts
<template>
<KeepAlive>
<component
:is="currentComponent"
:message="dynamicMessage"
@some-event="handleEvent"
/>
</KeepAlive>
</template>
<script setup lang="ts">
import { ref, shallowRef, markRaw, type Component, computed } from 'vue';
import ComponentA from './ComponentA.vue';
import ComponentB from './ComponentB.vue';
// ... 组件映射和 currentComponent 同上 ...
const componentsMap: Record<string, Component> = { /* ... */ };
const currentComponent = shallowRef<Component>(componentsMap.ComponentA);
const switchComponent = (componentName: string) => { /* ... */ }
// 1. 准备要传递的 props
const dynamicMessage = ref<string>('这是动态消息');
// 2. 准备事件处理函数
const handleEvent = (payload: any) => {
console.log('动态组件触发了事件:', payload);
}
</script>
注意 :如果
ComponentA和ComponentB接受的 props 不同,可能需要根据currentComponent的值来动态计算要传递的 props 对象。
4.常见使用场景
动态组件在需要灵活切换UI视图的场景中非常有用:
-
标签页
- 这是最经典的用法。你有一个标签栏,点击不同的标签,下方的内容区域就渲染对应的组件,同时使用
<KeepAlive>缓存已加载的标签页内容。
- 这是最经典的用法。你有一个标签栏,点击不同的标签,下方的内容区域就渲染对应的组件,同时使用
-
多步骤表单/向导
- 在一个多步骤的注册流程或配置向导中,"上一步" 和 "下一步" 按钮只是切换
<component :is="...">绑定的组件(Step1.vue,Step2.vue...),而无需切换路由。
- 在一个多步骤的注册流程或配置向导中,"上一步" 和 "下一步" 按钮只是切换
-
仪表盘小部件
- 允许用户自定义他们的仪表盘,从一个列表中选择不同的小部件(如"天气组件"、"图表组件"、"待办事项组件")。这些小部件都可以在同一个插槽中通过动态组件来渲染。
-
根据数据类型显示不同视图
- 比如你有一个列表,列表项的类型不同(例如,文章、视频、广告),你可以根据列表项的类型(
item.type)动态渲染不同的展示组件。
- 比如你有一个列表,列表项的类型不同(例如,文章、视频、广告),你可以根据列表项的类型(
总之,当你需要在一块区域根据条件或用户交互来"替换"整个组件时,动态组件就是你的首选方案。