Vue3动态组件&异步组件用法及说明
作者:难渡-
动态组件
动态组件是通过 <component :is="组件标识"> 语法,让 Vue 动态渲染不同组件的特性 —— 你只需修改 :is 绑定的值,Vue 就会自动替换渲染的组件,无需写大量 v-if/v-else 分支。
核心特点
- 一个
<component>标签即可渲染任意组件; - 支持绑定「组件对象」「组件名称(全局注册)」「异步组件」;
- 配合
<KeepAlive>可保留组件状态(如输入框内容、滚动位置); - 可以根据不同的条件灵活切换显示不同的组件,核心是通过component标签和is属性;
- 可以动态传递props/事件。
基础用法
1. 核心语法:绑定组件对象
Vue3 中 <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) // 默认渲染 CompA
</script>2. 绑定全局组件名称
如果组件已全局注册(在 main.js 中注册),可绑定字符串名称:
// 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> 可缓存组件实例,保留状态:
<template>
<div>
<button @click="currentComp = CompA">组件A</button>
<button @click="currentComp = CompB">组件B</button>
<!-- KeepAlive 缓存组件,切换后输入框内容不丢失 -->
<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('') // 状态会被 KeepAlive 保留
</script>
KeepAlive 高级配置:
- -
include:仅缓存指定组件(支持字符串 / 正则 / 数组); - -
exclude:排除不需要缓存的组件; - -
max:限制缓存组件的最大数量(避免内存溢出)。
2. 动态传参:Props / 事件透传
动态组件支持像普通组件一样传递 Props 和监听事件:
<template>
<component
:is="currentComp"
:msg="parentMsg"
@change="handleCompChange"
></component>
</template>
<script setup>
import { ref } from 'vue'
import CompA from './CompA.vue'
const currentComp = ref(CompA)
const parentMsg = ref('父组件传递的消息')
const handleCompChange = (val) => {
console.log('子组件触发的事件:', val)
}
</script>
3. 异步加载:结合 defineAsyncComponent
动态组件 + 异步组件 = 「按需加载 + 动态切换」,优化首屏性能:
<template>
<component :is="currentComp"></component>
</template>
<script setup>
import { ref, defineAsyncComponent } from 'vue'
// 异步加载组件(切换时才加载代码)
const CompA = defineAsyncComponent(() => import('./CompA.vue'))
const CompB = defineAsyncComponent(() => import('./CompB.vue'))
const currentComp = ref(CompA)
</script>
带加载 / 错误状态的异步组件:
<script setup>
import { ref, defineAsyncComponent } from 'vue'
import Loading from './Loading.vue'
import Error from './Error.vue'
// 配置异步组件的加载/错误状态
const CompA = defineAsyncComponent({
loader: () => import('./CompA.vue'),
loadingComponent: Loading, // 加载中显示
errorComponent: Error, // 加载失败显示
delay: 200, // 延迟显示加载组件(避免闪屏)
timeout: 3000 // 超时时间
})
const currentComp = ref(CompA)
</script>
4. 动态组件的生命周期
被 <KeepAlive> 缓存的动态组件,会触发两个特殊生命周期:
activated:组件被激活(切换显示)时触发;deactivated:组件被失活(切换隐藏)时触发。
<!-- CompA.vue -->
<script setup>
import { onActivated, onDeactivated } from 'vue'
onActivated(() => {
console.log('CompA 被激活(显示)')
})
onDeactivated(() => {
console.log('CompA 被失活(隐藏)')
})
</script>
异步组件
异步组件是指组件在需要渲染时才会被加载(而非页面初始化时) 的组件,底层依赖 ES6 的 import() 动态导入语法和 Vue 提供的 defineAsyncComponent 封装函数。
核心价值
- 拆分代码包体积,首屏只加载核心代码,非核心组件按需加载;
- 降低首屏加载时间,提升页面响应速度;
- 配合动态组件、路由懒加载,实现更灵活的组件加载策略。
基础用法
1.最简写法
通过 defineAsyncComponent 包裹 import() 函数,返回异步组件对象:
<template>
<!-- 像普通组件一样使用异步组件 -->
<AsyncComp />
</template>
<script setup>
import { defineAsyncComponent } from 'vue'
// 定义异步组件(最简写法)
const AsyncComp = defineAsyncComponent(() => {
// import() 动态导入组件,返回 Promise
return import('./AsyncComp.vue')
})
</script>
2.核心逻辑解析
import('./AsyncComp.vue'):ES6 动态导入,返回一个 Promise,只有当组件需要渲染时才会执行这个导入操作;defineAsyncComponent:Vue 封装的异步组件处理函数,接收加载函数 / 配置对象,返回一个 “包装器组件”(同步组件),Vue 会先渲染包装器,等真实组件加载完成后再替换。
进阶配置
实际开发中,需要处理「加载中、加载失败、超时、延迟」等场景,defineAsyncComponent 支持完整的配置项:
<template>
<AsyncComp />
</template>
<script setup>
import { defineAsyncComponent } from 'vue'
// 导入加载/错误兜底组件
import LoadingComp from './LoadingComp.vue'
import ErrorComp from './ErrorComp.vue'
// 完整配置的异步组件
const AsyncComp = defineAsyncComponent({
// 1. 核心:加载函数(返回 Promise)
loader: () => import('./AsyncComp.vue'),
// 2. 加载中显示的组件(可选)
loadingComponent: LoadingComp,
// 延迟显示加载组件(避免闪屏,默认 200ms)
delay: 200,
// 3. 加载失败显示的组件(可选)
errorComponent: ErrorComp,
// 加载失败的重试逻辑(可选)
onError: (err, retry, fail, attempts) => {
// err:错误信息
// retry:重试加载的函数
// fail:终止加载的函数
// attempts:已重试次数
if (attempts < 3) {
// 重试 3 次
setTimeout(retry, 1000)
} else {
// 超过 3 次则失败
fail()
}
},
// 4. 超时时间(可选,毫秒)
timeout: 5000, // 5 秒加载不完成则触发失败
})
</script>
配置项说明
| 配置项 | 类型 | 作用 | 默认值 |
|---|---|---|---|
| loader | Function | 动态导入组件的函数(必须返回 Promise) | - |
| loadingComponent | Component | 加载中显示的兜底组件 | undefined |
| delay | Number | 延迟显示加载组件的时间(避免闪屏) | 200 |
| errorComponent | Component | 加载失败显示的兜底组件 | undefined |
| onError | Function | 加载失败的回调(支持重试) | undefined |
| timeout | Number | 加载超时时间(超时触发失败) | Infinity |
常见使用场景
1.结合动态组件使用(按需切换加载)
异步组件 + 动态组件 = 「切换时才加载组件代码」,适合 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'
import { defineAsyncComponent } from 'vue'
import Loading from '@/components/Loading.vue'
// 路由懒加载(方式1:最简)
const Home = () => import('@/views/Home.vue')
// 路由懒加载(方式2:带配置)
const About = defineAsyncComponent({
loader: () => import('@/views/About.vue'),
loadingComponent: Loading,
delay: 100
})
const router = createRouter({
history: createWebHistory(),
routes: [
{ path: '/', component: Home },
{ path: '/about', component: About }
]
})
export default router
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 的核心逻辑是返回一个「包装器组件」:
- 1.包装器组件先渲染
loadingComponent(延迟后); - 2.执行 loader 函数加载真实组件;
- 3.加载成功:替换为真实组件;
- 4.加载失败 / 超时:渲染
errorComponent。
简化版伪代码:
function defineAsyncComponent(options) {
return {
setup() {
const state = ref('loading') // loading/success/error
const component = ref(null)
// 执行加载
options.loader()
.then(comp => {
component.value = comp.default
state.value = 'success'
})
.catch(() => state.value = 'error')
return { state, component }
},
render() {
if (this.state === 'loading') return h(options.loadingComponent)
if (this.state === 'error') return h(options.errorComponent)
return h(this.component)
}
}
}
总结
1.核心语法:defineAsyncComponent 包裹 import() 是异步组件的基础,支持最简写法和完整配置;
2.核心配置:重点掌握 loadingComponent/errorComponent/delay/timeout,处理加载状态;
3.核心场景:路由懒加载、动态组件切换、条件渲染的非核心组件;
4.优化原则:按需拆分、做好兜底、避免过度拆分。
5.简单记:异步组件 = 按需加载 + 状态兜底 + 体积优化,是 Vue 项目性能优化的 “标配” 手段。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。
