路由懒加载

路由懒加载

文章目录

  • 前言
  • 一、基本原理
    • 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')}]

工作流程

  1. 构建工具(Webpack / Vite)为每个动态import()生成独立 chunk 文件(带 content hash)
  2. 首屏只加载当前路由所需代码,其余 chunk 不下载
  3. 用户导航到对应路由时,浏览器再请求该 chunk
  4. 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

对比项preloadprefetch
加载时机与父 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 / 全局 loadingloading / 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 状态,两者场景不同。


七、易混淆点

  1. 懒加载 ≠ 不加载:首次进入该路由仍会请求 chunk,只是不在首屏加载。
  2. chunk 会缓存:除非 hash 变化,否则不会重复下载。
  3. defineAsyncComponent 不替代路由懒加载:路由直接用() => import()更简洁。
  4. import.meta.glob 默认懒加载:不加eager: true时返回的都是动态 import 函数。
  5. prefetch 不是立即加载:在浏览器空闲时才预取,不要与 preload 混用场景。

八、思考与练习

1.路由懒加载的原理是什么?

解析:路由 component 使用动态import(),构建工具拆成独立 chunk;导航到该路由时才下载并执行,减小首屏 bundle。

2.prefetch 和 preload 如何选择?

解析:preload 用于当前页面即将需要的模块(高优先级并行);prefetch 用于其他路由页面,空闲时预取。

3.懒加载后第二次访问还会请求吗?

解析:不会重复请求,浏览器已缓存该 chunk;发版后 hash 变化才会重新下载。

4.如何给路由切换加 Loading 效果?

解析:常用 NProgress 配合路由守卫(beforeEach 开始、afterEach 结束),或使用defineAsyncComponentloadingComponent配置加载态组件。

5.Vite 如何批量注册懒加载路由?

constmodules=import.meta.glob('../views/**/*.vue')// 遍历 modules 生成 routes 数组

总结

  • 原理:动态import()按路由拆分 chunk,访问时才加载,减小首屏体积
  • 魔法注释webpackChunkName命名、webpackPrefetch空闲预取、webpackPreload并行加载
  • import.meta.glob:Vite 批量扫描页面,自动生成懒加载路由
  • 选型:路由用() => import();非路由异步组件用defineAsyncComponent