异步组件
把组件的加载时机从应用启动阶段推迟到真正需要渲染时。
一、异步组件是什么
普通组件通常会随着入口模块一起进入构建产物。异步组件使用动态 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" />
当 showPanel 为 false 时,组件不会创建,也不会触发加载;变为 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
一句话总结:异步组件不是简单地“延迟渲染”,而是把组件加载、网络状态和错误恢复纳入组件生命周期。