在 Vue3 项目开发中,动态组件和异步组件是提升代码灵活性与性能的常用技术。动态组件通过 <component :is="..."> 实现不同组件的切换,避免大量 v-if/v-else;异步组件则利用 import() 实现按需加载,减少首屏体积。本文结合实际代码和配置参数,详细拆解这两类组件的正确用法与最佳实践。
一、动态组件核心用法
1. 绑定组件对象(推荐)
在 <script setup> 中,组件不会自动注册为全局名称,直接绑定导入的组件对象是最稳写法:
- <template>
- <div>
- <button @click="currentComp = CompA">组件A</button>
- <button @click="currentComp = CompB">组件B</button>
- <button @click="currentComp = CompC">组件C</button>
- <component :is="currentComp"></component>
- </div>
- </template>
- <script setup>
- import { ref } from 'vue'
- import CompA from './CompA.vue'
- import CompB from './CompB.vue'
- import CompC from './CompC.vue'
- const currentComp = ref(CompA)
- </script>
复制代码
2. 绑定全局组件名称
若在 main.js 中通过 app.component() 全局注册过组件,可绑定字符串名称:
- // main.js
- import { createApp } from 'vue'
- import App from './App.vue'
- import CompA from './CompA.vue'
- const app = createApp(App)
- app.component('CompA', CompA)
- app.mount('#app')
- // 组件内
- <template>
- <component :is="currentCompName"></component>
- </template>
- <script setup>
- import { ref } from 'vue'
- const currentCompName = ref('CompA')
- </script>
复制代码
二、进阶技巧:状态保留与参数传递
1. 配合 <KeepAlive> 保留组件状态
默认切换时,旧组件会被销毁、新组件重建(输入框内容、滚动位置等状态丢失)。用 <KeepAlive> 包裹 <component> 后,组件实例会被缓存,切换回来时状态保留:
- <template>
- <div>
- <button @click="currentComp = CompA">组件A</button>
- <button @click="currentComp = CompB">组件B</button>
- <KeepAlive>
- <component :is="currentComp"></component>
- </KeepAlive>
- </div>
- </template>
- <!-- CompA.vue -->
- <template>
- <input v-model="inputVal" placeholder="组件A输入内容" />
- </template>
- <script setup>
- import { ref } from 'vue'
- const inputVal = ref('')
- </script>
复制代码
KeepAlive 还支持 include(包含)、exclude(排除)和 max(最大缓存数量)等高级配置。被缓存的组件会触发 onActivated 和 onDeactivated 两个特殊生命周期:
- <script setup>
- import { onActivated, onDeactivated } from 'vue'
- onActivated(() => { console.log('组件被激活') })
- onDeactivated(() => { console.log('组件被失活') })
- </script>
复制代码
2. 动态传参与事件监听
动态组件可以像普通组件一样传递 props 和绑定事件,只需在 <component> 上直接写属性和 @ 事件:
- <template>
- <component :is="currentComp" :msg="parentMsg" @change="handleChange"></component>
- </template>
- <script setup>
- import { ref } from 'vue'
- import CompA from './CompA.vue'
- const currentComp = ref(CompA)
- const parentMsg = ref('从父组件传来的消息')
- const handleChange = (val) => { console.log('子组件事件:', val) }
- </script>
复制代码
三、异步组件:按需加载与配置
异步组件通过 defineAsyncComponent 包裹 import() 实现,组件在需要渲染时才发起网络请求加载代码,核心价值是拆分代码包、降低首屏加载时间。
1. 最简用法
- <template>
- <AsyncComp />
- </template>
- <script setup>
- import { defineAsyncComponent } from 'vue'
- const AsyncComp = defineAsyncComponent(() => import('./AsyncComp.vue'))
- </script>
复制代码
defineAsyncComponent 接收一个返回 Promise 的加载函数,Vue 会自动处理组件的挂载与替换。
2. 完整配置(加载/错误/超时/重试)
实际开发中需要处理加载中、加载失败、超时等场景:
- <template>
- <AsyncComp />
- </template>
- <script setup>
- import { defineAsyncComponent } from 'vue'
- import LoadingComp from './LoadingComp.vue'
- import ErrorComp from './ErrorComp.vue'
- const AsyncComp = defineAsyncComponent({
- loader: () => import('./AsyncComp.vue'),
- loadingComponent: LoadingComp, // 加载中显示的组件
- delay: 200, // 延迟显示加载组件(防闪屏)
- errorComponent: ErrorComp, // 加载失败显示的组件
- timeout: 5000, // 超时时间(毫秒)
- onError: (err, retry, fail, attempts) => {
- if (attempts < 3) {
- setTimeout(retry, 1000) // 重试3次
- } else {
- fail() // 放弃加载
- }
- }
- })
- </script>
复制代码
参数说明:loader 是必需的加载函数;loadingComponent 和 delay 控制加载中状态;errorComponent 和 onError 处理失败;timeout 设置超时。
四、常见使用场景
1. 动态组件 + 异步组件(Tab切换按需加载)
切换 Tab 时才加载对应组件代码,非常适合标签页或步骤条场景:
- <template>
- <button @click="currentComp = AsyncTab1">标签1</button>
- <button @click="currentComp = AsyncTab2">标签2</button>
- <component :is="currentComp"></component>
- </template>
- <script setup>
- import { ref, defineAsyncComponent } from 'vue'
- const AsyncTab1 = defineAsyncComponent(() => import('./Tab1.vue'))
- const AsyncTab2 = defineAsyncComponent(() => import('./Tab2.vue'))
- const currentComp = ref(AsyncTab1)
- </script>
复制代码
2. 路由懒加载
在 Vue Router 中结合异步组件实现按需加载,是项目性能优化的标配:
- // router/index.js
- import { createRouter, createWebHistory } from 'vue-router'
- const Home = () => import('@/views/Home.vue')
- const About = {
- loader: () => import('@/views/About.vue'),
- loadingComponent: () => import('@/components/Loading.vue')
- }
- const routes = [
- { path: '/', component: Home },
- { path: '/about', component: About }
- ]
- export default createRouter({ history: createWebHistory(), routes })
复制代码
3. 条件渲染的非核心组件
比如弹窗、抽屉等组件,仅在触发显示时才加载代码:
- <template>
- <button @click="showModal = true">打开弹窗</button>
- <AsyncModal v-if="showModal" @close="showModal = false" />
- </template>
- <script setup>
- import { ref, defineAsyncComponent } from 'vue'
- const showModal = ref(false)
- const AsyncModal = defineAsyncComponent(() => import('./Modal.vue'))
- </script>
复制代码
五、原理补充
defineAsyncComponent 返回一个“包装器组件”,内部逻辑简化为:
- 先渲染 loadingComponent(带 delay 延迟);
- 执行 loader(import())加载真实组件;
- 成功则替换为真实组件,失败则渲染 errorComponent;
- 支持超时和重试。
六、总结
动态组件和异步组件是 Vue3 项目开发中不可或缺的“灵活+性能”利器。核心记住:
- 动态组件用 <component :is="组件对象" /> 结合 KeepAlive 保留状态;
- 异步组件用 defineAsyncComponent + import() 按需加载,配合 loadingComponent、errorComponent、delay、timeout 做好兜底;
- 优先应用于路由懒加载、标签页切换、条件渲染等场景。
按需拆分、做好异常处理、避免过度拆分,即可有效优化首屏体积与用户体验。 |