异步组件

把组件的加载时机从应用启动阶段推迟到真正需要渲染时。

一、异步组件是什么

普通组件通常会随着入口模块一起进入构建产物。异步组件使用动态 import() 加载组件,构建工具可以将它拆成独立 chunk:

加载应用入口
访问到异步组件
下载组件 chunk
加载并渲染组件

它主要解决首屏包体积和非首屏功能延迟加载问题,但会引入加载状态、网络失败和额外请求,因此不应该把所有组件都异步化。

二、基本用法

<script setup>
import { defineAsyncComponent } from 'vue'
import LoadingView from './LoadingView.vue'
import ErrorView from './ErrorView.vue'

const AsyncPanel = defineAsyncComponent({
  loader: () => import('./Panel.vue'),
  loadingComponent: LoadingView,
  errorComponent: ErrorView,
  delay: 200,
  timeout: 10000,
})
</script>

<template>
  <AsyncPanel />
</template>

常用配置:

配置作用
loader返回组件 Promise 的加载函数
loadingComponent加载中的占位组件
errorComponent加载失败后的错误组件
delay延迟显示 loading,避免快速加载时闪烁
timeout超过指定时间仍未完成时视为失败
onError自定义重试、失败和错误处理逻辑

错误重试

网络不稳定时可以有限次数重试,但必须设置上限,并给用户提供可操作的失败状态:

const AsyncPanel = defineAsyncComponent({
  loader: () => import('./Panel.vue'),
  onError(error, retry, fail, attempts) {
    if (attempts <= 2) {
      retry()
    } else {
      fail()
      console.error('Panel 加载失败', error)
    }
  },
})

重试不能解决服务端持续 404、版本发布导致的 chunk 不存在等问题。此时应展示错误信息,必要时引导用户刷新页面或重新进入应用。

三、条件渲染和异步组件的区别

<AsyncPanel v-if="showPanel" />

showPanelfalse 时,组件不会创建,也不会触发加载;变为 true 后才会执行 loader。组件第一次加载成功后,模块通常会被浏览器和运行时缓存,后续再次显示不会重复下载。

如果希望应用启动时就提前下载,可以使用预加载策略或在合适的交互时机主动触发加载,但这会牺牲部分首屏网络资源,应结合实际数据验证。

四、路由懒加载

路由页面是最常见的异步组件使用场景:

const routes = [
  {
    path: '/about',
    component: () => import('./views/About.vue'),
  },
  {
    path: '/settings',
    component: () => import('./views/Settings.vue'),
  },
]

导航到 /settings 时,路由器才加载对应页面 chunk。路由懒加载本质上仍然是动态 import(),只是组件加载时机由路由导航触发。

页面切换时还要处理:

  • chunk 加载中的 loading 状态;
  • chunk 下载失败时的错误提示和重试;
  • 用户快速切换路由时,旧导航结果不能覆盖新导航;
  • 发布新版本后旧页面引用的 chunk 不存在时的刷新策略。

五、配合 Suspense

<Suspense> 用于统一等待异步组件或带 async setup() 的组件,并在完成前显示 fallback:

<Suspense>
  <template #default>
    <AsyncPanel />
  </template>

  <template #fallback>
    <LoadingView />
  </template>
</Suspense>

两者职责不同:

能力defineAsyncComponent<Suspense>
加载组件否,负责等待子树中的异步依赖
单个组件 loading/error支持配置主要通过 fallback 处理 loading
重试策略可通过 onError 配置不负责具体请求重试
等待 async setup不负责支持

Suspense 适合统一管理一棵异步子树的等待状态;单个组件需要独立的错误、超时和重试 UI 时,仍应配置异步组件本身。其详细说明见 Suspense

六、异步组件和动态组件不是一回事

动态组件是在多个已经加载的组件之间切换:

<component :is="currentComponent" />

异步组件则描述组件如何被加载:

const AsyncPanel = defineAsyncComponent(() => import('./Panel.vue'))

两者可以组合使用,但关注点不同:

动态组件:现在渲染哪个组件
异步组件:组件什么时候、如何加载

七、SSR 和使用边界

异步组件可以参与 SSR,但服务端和客户端必须能够解析到相同的组件和产物,避免 hydration 不一致。对于首屏关键内容,应确认服务端输出、客户端接管、loading 状态和错误状态都经过验证。

适合异步加载:

  • 非首屏路由;
  • 打开弹窗后才出现的重量级组件;
  • 图表、编辑器、地图等低频且体积较大的功能;
  • 管理后台中不常访问的功能模块。

不适合盲目异步加载:

  • 首屏立刻可见的小组件;
  • 高频切换且每次都需要等待的内容;
  • 体积很小、拆分后请求成本高于收益的组件。

八、排查与优化建议

  • 用构建产物分析确认组件是否真的被拆成独立 chunk;
  • 检查公共依赖是否被合理提取,避免大依赖重复进入多个 chunk;
  • 为 loading 设置合理的 delay,避免短请求造成闪烁;
  • 为失败场景提供重试或刷新入口,不要只显示空白区域;
  • 在真实网络环境下测试慢网、断网、缓存失效和版本更新;
  • 记录异步 chunk 的加载成功率和耗时,再决定是否调整拆包策略。

总结

defineAsyncComponent
  └── 定义组件的异步加载、loading、error、timeout 和 retry

动态 import()
  └── 让构建工具拆分 chunk,运行时按需下载

Suspense
  └── 统一等待异步组件或 async setup,并显示 fallback

一句话总结:异步组件不是简单地“延迟渲染”,而是把组件加载、网络状态和错误恢复纳入组件生命周期。