随着应用功能增长,打包后的 JavaScript 文件会越来越大。如果用户访问首页时就要下载整个应用的所有代码,首屏加载时间会被严重拖慢——尤其对于移动网络或弱网用户,几 MB 的 JS 可能意味着好几秒的白屏。
路由懒加载(Route Lazy Loading)是解决这个问题的核心手段:只在用户真正访问某个路由时,才去加载该路由对应的组件代码。配合构建工具的代码分割(Code Splitting),每个路由的组件会被拆成独立的 chunk 文件,按需请求。
基础实现:动态导入
Vue Router 直接支持动态导入语法 import(),它返回一个 Promise,Vue Router 会在路由被访问时自动解析这个 Promise 并渲染组件。
同步加载(不推荐):
// router/index.js
import Home from '@/views/Home.vue'
import About from '@/views/About.vue'
const routes = [
{ path: '/', component: Home },
{ path: '/about', component: About }
]
这种写法会把 Home.vue 和 About.vue 都打进主包(app.js),用户访问首页时,About 页面的代码也被下载了,即便他可能永远不会点进去。
懒加载(推荐):
const routes = [
{ path: '/', component: () => import('@/views/Home.vue') },
{ path: '/about', component: () => import('@/views/About.vue') }
]
Vite 或 Webpack 在构建时遇到 import() 会将该文件提取为一个独立的 chunk(如 About-abc123.js)。用户访问 / 时只加载主页 chunk,只有当用户点击“关于”链接时,浏览器才会发起请求加载 About-abc123.js 并渲染。
带加载状态与错误处理
网络请求可能较慢或失败,单纯的 () => import(...) 无法提供过渡反馈。可以结合 Vue 3 的 defineAsyncComponent 进行更精细的控制:
import { defineAsyncComponent } from 'vue'
const About = defineAsyncComponent({
loader: () => import('@/views/About.vue'),
loadingComponent: LoadingSpinner, // 加载中显示的组件
errorComponent: ErrorDisplay, // 加载失败显示的组件
delay: 200, // 200ms 后才显示 loading,避免闪烁
timeout: 10000 // 超时视为失败
})
const routes = [
{ path: '/about', component: About }
]
对于大多数场景,直接使用 () => import(...) 已经足够,只有在需要定制加载/错误 UI 时才用到 defineAsyncComponent。
分组打包(Magic Comments)
默认每个 import() 会生成一个独立的 chunk。有时你希望把几个相关路由的组件打包在同一个 chunk 中(比如同一业务模块下的子页面),可以用魔法注释指定 webpackChunkName 或 vite 对应的输出文件名。
const routes = [
{
path: '/settings',
component: () => import(/* webpackChunkName: "settings" */ '@/views/Settings.vue')
},
{
path: '/settings/profile',
component: () => import(/* webpackChunkName: "settings" */ '@/views/Profile.vue')
}
]
构建后,这两个组件会被合并到一个名为 settings.[hash].js 的 chunk 里。这样做的好处是:当用户进入设置模块时,相关的子页面代码可以一次取回,减少后续导航的网络请求次数。注意:不要把所有路由都塞进一个 chunk,那就失去了懒加载的意义。
路由懒加载的实践建议
- 默认全部懒加载:除了应用最核心的布局组件或全局通用的组件外,几乎所有路由页面都应该写成动态导入。这是性价比最高的优化之一。
- 首屏考虑预加载:对于用户从首页大概率会立即跳转的关键页面(比如登录后直接进入的仪表盘),可以在首页加载完成后利用
<link rel="prefetch">或router.isReady()后手动预加载,让后续跳转变瞬时。 - 不要为了拆而拆:过度的分割会导致过多的 HTTP 请求(在 HTTP/1.1 下尤其明显)。好在现代构建工具和 HTTP/2 已经极大缓解了这个问题,但仍建议按业务模块合理分组。
- 监控 chunk 大小:用
rollup-plugin-visualizer或webpack-bundle-analyzer分析构建产物,确保单个 chunk 体积不会过大(如超过 500KB,视项目可接受标准而定)。
懒加载本质上是用按需获取来换取首屏速度,是移动端优化的必选项,也是 Vue 项目性能基线的一部分。Vue Router 对动态导入的原生支持让这个优化实施成本几乎为零。