人人都会AI编程

组件级异步加载

更新时间:2026-07-10

前面讲了路由级别的代码分割,它能让用户“访问哪个页面才加载哪个页面的代码”。但即使在一个页面内部,也可能存在某些组件不需要立即渲染——比如一个复杂的图表、一个厚重的富文本编辑器、一个点击按钮才弹出的模态框。这时候就需要组件级异步加载,“什么时候用,什么时候加载”。

基础用法:defineAsyncComponent

Vue 3 提供了 defineAsyncComponent 函数,用于定义一个异步组件。它接收一个返回 Promise 的加载函数,这个 Promise 应该 resolve 一个组件:

<script setup>
import { defineAsyncComponent } from 'vue'

// 异步加载,只有这个组件真正被渲染时才会请求对应的 js 文件
const HeavyChart = defineAsyncComponent(() =>
  import('./components/HeavyChart.vue')
)

const showChart = ref(false)
</script>

<template>
  <button @click="showChart = true">显示图表</button>
  <HeavyChart v-if="showChart" />
</template>

showChartfalse 时,HeavyChart 组件不会被渲染,它的代码也不会被下载。只有用户点击按钮后,v-if 条件成立,浏览器才会去请求 HeavyChart.vue 对应的 JS 打包文件,拿到后再渲染。这与直接 import HeavyChart from './components/HeavyChart.vue' 有本质区别——后者会把组件的代码打包进当前的入口 chunk 中,无论它是否被使用。

处理加载态与错误态

异步请求总有失败的概率,而且网络慢时用户可能会盯着空白区域等待。defineAsyncComponent 支持传入一个完整的配置对象,覆盖加载的各个阶段:

<script setup>
import { defineAsyncComponent, h } from 'vue'

const HeavyChart = defineAsyncComponent({
  loader: () => import('./components/HeavyChart.vue'),
  // 加载中显示的组件
  loadingComponent: h('div', '图表加载中...'),
  // 显示加载组件前的延迟时间(ms),避免闪烁
  delay: 200,
  // 加载失败时显示的组件
  errorComponent: h('div', '图表加载失败,请重试'),
  // 超时时间(ms),超时后会走 errorComponent
  timeout: 5000
})
</script>

参数说明

  • loader:异步加载函数,与简洁写法的唯一参数一样。
  • loadingComponent:加载过程中展示的占位组件,通常放一个骨架屏或简单的提示文字。
  • delay:延迟时间,默认 200ms。如果加载在 200ms 内完成,loadingComponent 不会出现,避免组件加载很快时出现“一闪而过”的割裂感。
  • errorComponent:加载失败(网络错误、超时)时展示的组件。
  • timeout:超时阈值,默认 Infinity。超过这个时间未加载完成视为失败。

如果你的项目有统一的加载/错误视觉规范,可以把这段配置封装成一个工厂函数:

// utils/asyncComponent.js
import { defineAsyncComponent } from 'vue'
import LoadingSkeleton from '@/components/LoadingSkeleton.vue'
import ErrorRetry from '@/components/ErrorRetry.vue'

export function createAsyncComponent(loader) {
  return defineAsyncComponent({
    loader,
    loadingComponent: LoadingSkeleton,
    errorComponent: ErrorRetry,
    delay: 200,
    timeout: 10000
  })
}

// 使用
const HeavyChart = createAsyncComponent(() => import('./components/HeavyChart.vue'))

常见使用场景

条件渲染的“重量级”组件
模态框、抽屉、弹窗等“不打开就不存在”的组件,非常适合异步加载。一个后台管理系统的详情抽屉里可能嵌了一个 ECharts 图表,但绝大多数用户只会浏览列表,不会点进去看详情。这时把图表组件异步化,能省下首屏下载图表库的体积。

依赖第三方大库的组件
如果你封装了一个富文本编辑器组件,它依赖 @tiptap/core 等各种包,加起来可能上百 KB。直接 import 会让所有用户为它买单,哪怕他们只使用表单中的纯文本输入框。异步化这个组件,体积开销就变成了“用到了才支付”。

页面内按需激活的模块
一个仪表盘页面可能有 5 个图表区域,但用户通常只关注前两个。可以把后面图表区域对应的组件设为异步,当用户滚动到可视区域时再触发加载——配合 IntersectionObserver,体验会更丝滑。

与 Suspense 的配合

Vue 3 内置的 Suspense 组件可以统一管理异步组件的加载状态,适合多个异步组件共同出现的场景:

<template>
  <Suspense>
    <template #default>
      <HeavyChart />
      <RichEditor />
    </template>
    <template #fallback>
      <div>内容加载中...</div>
    </template>
  </Suspense>
</template>

HeavyChartRichEditor 都在异步加载时,Suspense 会展示 #fallback 插槽的内容(一个统一的加载提示或骨架屏),等它们都加载完成后才替换为真实内容。这种方式比在每个组件内部各自处理 loading 更集中、更易维护,但注意:Suspense 目前还属于实验性特性,在部分低版本浏览器中需要额外补丁,正式环境使用前建议查看官方文档确认稳定性。

打包产物的直观变化

异步组件会让打包工具自动产出独立的 chunk 文件。比如 HeavyChart.vue 打包后会生成类似 HeavyChart.a1b2c3d.js 的文件,浏览器只有在组件被渲染时才会发起请求。这种“按需索取”的效果和路由懒加载一样,只不过粒度更细——从“页面级”缩小到了“组件级”。