vue.js

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript类库 > vue.js > Vue3动态组件&异步组件

Vue3动态组件&异步组件用法及说明

作者:难渡-

动态组件通过`<component:is="组件标识">`语法实现不同组件的动态渲染,支持组件对象、全局组件名称、异步组件等多种形式,结合`<KeepAlive>`可保留组件状态,配合异步组件实现按需加载,优化首屏性能,通过`defineAsyncComponent`支持异步加载,提供加载状态处理和配置项优化

动态组件

动态组件是通过 <component :is="组件标识"> 语法,让 Vue 动态渲染不同组件的特性 —— 你只需修改 :is 绑定的值,Vue 就会自动替换渲染的组件,无需写大量 v-if/v-else 分支。

核心特点

基础用法

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 高级配置:

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> 缓存的动态组件,会触发两个特殊生命周期:

<!-- 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.核心逻辑解析

进阶配置

实际开发中,需要处理「加载中、加载失败、超时、延迟」等场景,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>

配置项说明

配置项类型作用默认值
loaderFunction动态导入组件的函数(必须返回 Promise)-
loadingComponentComponent加载中显示的兜底组件undefined
delayNumber延迟显示加载组件的时间(避免闪屏)200
errorComponentComponent加载失败显示的兜底组件undefined
onErrorFunction加载失败的回调(支持重试)undefined
timeoutNumber加载超时时间(超时触发失败)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 的核心逻辑是返回一个「包装器组件」:

简化版伪代码:

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 项目性能优化的 “标配” 手段。

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

您可能感兴趣的文章:
阅读全文