文章目录
- 前言
- 一、基本原理
- 1.1 动态 import 拆分 chunk
- 1.2 与同步引入对比
- 二、Webpack 魔法注释
- 2.1 自定义 chunk 名称
- 2.2 prefetch:空闲时预加载
- 2.3 preload:并行高优先级加载
- 2.4 prefetch vs preload
- 三、Vite 批量注册路由
- 3.1 import.meta.glob
- 3.2 按模块分组
- 四、defineAsyncComponent 与路由懒加载
- 4.1 区别
- 五、典型场景
- 5.1 中后台按业务模块懒加载
- 5.2 权限路由动态加载
- 5.3 首屏同步 + 其余懒加载
- 六、面试聚焦
- 6.1 prefetch 与 preload 区别
- 6.2 懒加载的组件会重复请求吗?
- 6.3 路由懒加载 vs defineAsyncComponent
- 七、易混淆点
- 八、思考与练习
- 总结
前言
路由懒加载是 Vue SPA 首屏优化的常用手段,通过动态import()将页面组件拆成独立 chunk,访问时才加载。本篇会讲清楚:
- 路由懒加载的原理与写法
- Vite 批量注册与 prefetch / preload 区别
- defineAsyncComponent 与路由懒加载的场景区别
一、基本原理
1.1 动态 import 拆分 chunk
constroutes=[{path:'/',component:()=>import('@/views/Home.vue')// 懒加载},{path:'/user',component:()=>import('@/views/User.vue')},{path:'/order',component:()=>import('@/views/Order.vue')}]工作流程:
- 构建工具(Webpack / Vite)为每个动态
import()生成独立 chunk 文件(带 content hash) - 首屏只加载当前路由所需代码,其余 chunk 不下载
- 用户导航到对应路由时,浏览器再请求该 chunk
- chunk 下载完成后渲染页面组件
1.2 与同步引入对比
// ❌ 同步引入:所有页面打包进主 bundle,首屏体积大importHomefrom'@/views/Home.vue'importUserfrom'@/views/User.vue'importOrderfrom'@/views/Order.vue'// ✅ 懒加载:按路由拆分,首屏只加载 Homecomponent:()=>import('@/views/User.vue')| 对比项 | 同步引入 | 路由懒加载 |
|---|---|---|
| 首屏体积 | 大(全部页面) | 小(当前页面) |
| 加载时机 | 应用启动时 | 导航到该路由时 |
| 适用场景 | 首屏必需页面 | 非首屏业务页面 |
二、Webpack 魔法注释
2.1 自定义 chunk 名称
constroutes=[{path:'/order',component:()=>import(/* webpackChunkName: "order" */'@/views/Order.vue')}]// 生成文件:order.[hash].js(便于调试和缓存分析)2.2 prefetch:空闲时预加载
{path:'/dashboard',component:()=>import(/* webpackPrefetch: true */'@/views/Dashboard.vue')}浏览器在空闲时提前下载该 chunk,用户后续导航时可更快渲染。适合「很可能接下来会访问」的页面。
2.3 preload:并行高优先级加载
{path:'/critical',component:()=>import(/* webpackPreload: true */'@/views/Critical.vue')}与父 chunk并行加载,优先级高。适合当前页面很快就要用到的模块。
2.4 prefetch vs preload
| 对比项 | preload | prefetch |
|---|---|---|
| 加载时机 | 与父 chunk 并行 | 浏览器空闲时 |
| 优先级 | 高 | 低 |
| 用途 | 当前页面即将需要 | 未来可能访问 |
| 典型场景 | 首屏关键子模块 | 其他路由页面 |
<!-- preload --><linkrel="preload"href="critical.js"as="script"><!-- prefetch --><linkrel="prefetch"href="dashboard.js"as="script">Vite 生产构建同样支持这些 magic comment,行为与 Webpack 类似。
三、Vite 批量注册路由
3.1 import.meta.glob
页面较多时,可批量扫描并自动生成懒加载路由:
// router/routes.jsconstmodules=import.meta.glob('../views/**/*.vue')constroutes=Object.entries(modules).map(([path,component])=>{// '../views/user/List.vue' → '/user/list'constroutePath=path.replace('../views','').replace(/\.vue$/,'').replace(/\/index$/,'').toLowerCase()return{path:routePath||'/',component// 已是 () => import() 形式}})3.2 按模块分组
// 只扫描 admin 目录constadminModules=import.meta.glob('../views/admin/**/*.vue')// eager: true 则同步加载(非懒加载)constsyncModules=import.meta.glob('./dir/*.js',{eager:true})适合中后台系统大量页面模块的自动注册,减少手动维护 routes 数组。
四、defineAsyncComponent 与路由懒加载
4.1 区别
// 路由懒加载:Vue Router 直接使用动态 import{path:'/user',component:()=>import('@/views/User.vue')}// defineAsyncComponent:用于非路由场景的异步组件import{defineAsyncComponent}from'vue'constAsyncChart=defineAsyncComponent({loader:()=>import('@/components/Chart.vue'),loadingComponent:LoadingSpinner,errorComponent:ErrorDisplay,delay:200,timeout:10000})| 对比项 | 路由懒加载 | defineAsyncComponent |
|---|---|---|
| 使用场景 | 路由页面组件 | 弹窗、Tab、动态组件 |
| 加载配置 | NProgress / 全局 loading | loading / error / timeout |
| 代码分割 | 按路由拆 chunk | 按组件拆 chunk |
路由场景直接用() => import()即可;需要 loading/error 降级时用defineAsyncComponent包装后再作为 component。
五、典型场景
5.1 中后台按业务模块懒加载
constroutes=[{path:'/user',component:()=>import('@/views/user/index.vue')},{path:'/order',component:()=>import('@/views/order/index.vue')},{path:'/report',component:()=>import('@/views/report/index.vue')}]5.2 权限路由动态加载
// 根据角色动态 addRoute,模块本身也是懒加载constadminRoutes=[{path:'/admin',component:()=>import('@/layouts/AdminLayout.vue'),children:[{path:'users',component:()=>import('@/views/admin/Users.vue')}]}]if(role==='admin'){adminRoutes.forEach(route=>router.addRoute(route))}5.3 首屏同步 + 其余懒加载
importHomefrom'@/views/Home.vue'// 首屏同步加载constroutes=[{path:'/',component:Home},{path:'/about',component:()=>import('@/views/About.vue')},{path:'/contact',component:()=>import('@/views/Contact.vue')}]首屏关键页面同步引入,其余全部懒加载,平衡 FCP 与总体积。
六、面试聚焦
6.1 prefetch 与 preload 区别
- preload:高优先级,与父 chunk 并行,当前页面很快需要
- prefetch:低优先级,浏览器空闲时加载,未来可能访问的路由
6.2 懒加载的组件会重复请求吗?
不会(正常情况下)。首次访问后 chunk 被浏览器缓存,后续导航直接使用缓存。只有构建后文件 hash 变化(发版更新)才会重新请求。
6.3 路由懒加载 vs defineAsyncComponent
路由懒加载用于页面级代码分割;defineAsyncComponent用于组件级异步加载,可配置 loading/error 状态,两者场景不同。
七、易混淆点
- 懒加载 ≠ 不加载:首次进入该路由仍会请求 chunk,只是不在首屏加载。
- chunk 会缓存:除非 hash 变化,否则不会重复下载。
- defineAsyncComponent 不替代路由懒加载:路由直接用
() => import()更简洁。 - import.meta.glob 默认懒加载:不加
eager: true时返回的都是动态 import 函数。 - prefetch 不是立即加载:在浏览器空闲时才预取,不要与 preload 混用场景。
八、思考与练习
1.路由懒加载的原理是什么?
解析:路由 component 使用动态import(),构建工具拆成独立 chunk;导航到该路由时才下载并执行,减小首屏 bundle。
2.prefetch 和 preload 如何选择?
解析:preload 用于当前页面即将需要的模块(高优先级并行);prefetch 用于其他路由页面,空闲时预取。
3.懒加载后第二次访问还会请求吗?
解析:不会重复请求,浏览器已缓存该 chunk;发版后 hash 变化才会重新下载。
4.如何给路由切换加 Loading 效果?
解析:常用 NProgress 配合路由守卫(beforeEach 开始、afterEach 结束),或使用defineAsyncComponent的loadingComponent配置加载态组件。
5.Vite 如何批量注册懒加载路由?
constmodules=import.meta.glob('../views/**/*.vue')// 遍历 modules 生成 routes 数组总结
- 原理:动态
import()按路由拆分 chunk,访问时才加载,减小首屏体积 - 魔法注释:
webpackChunkName命名、webpackPrefetch空闲预取、webpackPreload并行加载 - import.meta.glob:Vite 批量扫描页面,自动生成懒加载路由
- 选型:路由用
() => import();非路由异步组件用defineAsyncComponent