异步组件与代码分割
首屏不需要把所有组件都下载下来。Vue 提供 defineAsyncComponent 把「不常出现 / 很重」的组件拆成独立 chunk,用到时才加载。
1. defineAsyncComponent 基本用法
import { defineAsyncComponent } from 'vue';
const HeavyModal = defineAsyncComponent(() => import('./HeavyModal.vue'));() => import('./HeavyModal.vue') 是动态导入,打包工具(Vite/webpack)会把它单独切成一个 chunk 文件。只有当 HeavyModal 真正要渲染时,浏览器才去请求这个 chunk。
2. 高级配置:加载态 / 错误态 / 超时
const HeavyModal = defineAsyncComponent({
loader: () => import('./HeavyModal.vue'),
loadingComponent: Spinner, // 加载中显示的占位
errorComponent: ErrorBox, // 加载失败显示
delay: 200, // 加载快于 200ms 就不闪 loading
timeout: 5000, // 超时报错
});delay 避免「闪一下 loading 又立刻消失」的抖动;timeout 防止网络挂起时一直转圈。
3. Suspense:等待异步 setup / 异步组件
如果组件的 setup 是异步的(async setup),或用 <script setup> 里 await 了什么,可以用 <Suspense> 包一层,统一展示加载态:
<template>
<Suspense>
<template #default>
<UserProfile /> <!-- 内部有 await -->
</template>
<template #fallback>
<Spinner />
</template>
</Suspense>
</template><Suspense> 会等 #default 里的异步依赖都 settle 后再显示内容,期间展示 #fallback。它一次只能有一个根节点,且目前主要配合路由/异步组件使用。
4. 路由懒加载
和组件异步加载同理,路由也用动态导入拆块:
// router.ts
const routes = [
{ path: '/', component: () => import('./pages/Home.vue') },
{ path: '/admin', component: () => import('./pages/Admin.vue') },
];访问 /admin 时才去下载 Admin.vue 的 chunk。首屏只加载 Home 相关代码。
5. 和打包工具的关系
defineAsyncComponent / 路由懒加载本身不负责切分,真正切分是打包工具做的事:它看到 import() 动态导入,就生成独立 chunk。所以这套机制和 Vite/Rollup/webpack 强相关——写法是 Vue 的,切分是工具链的。
| 写法 | 生成的 chunk 时机 |
|---|---|
静态 import Comp from './C.vue' | 合并进当前 chunk |
defineAsyncComponent(() => import('./C.vue')) | 独立 chunk,渲染时才请求 |
路由 component: () => import('./Page.vue') | 独立 chunk,进入路由才请求 |
小结
defineAsyncComponent(() => import(...))把重组件拆成独立 chunk,按需加载- 配置项:
loadingComponent/errorComponent/delay/timeout处理各种加载态 <Suspense>统一等待异步setup/ 异步组件,期间显示#fallback- 路由懒加载
component: () => import(...)同理拆首屏 - 真正切分由打包工具(Vite/Rollup)完成
有个很重的 ReportModal.vue,只在用户点「导出报表」时才可能用到。把它从静态 import 改成异步组件,并加上「加载中」占位(用现成的 Spinner 组件)。
一个 Dashboard 组件在 <script setup> 里 await fetch('/api/metrics'),首屏期间想显示骨架屏。用 <Suspense> 实现。
把下面的路由配置改成「每个页面一个独立 chunk」,使首屏只加载首页代码。
import Home from './pages/Home.vue';
import Admin from './pages/Admin.vue';
const routes = [
{ path: '/', component: Home },
{ path: '/admin', component: Admin },
];