动态组件

动态组件是通过 <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 项目性能优化的 “标配” 手段。

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

声明:本站(华域联盟www.cnhackhy.com)所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。