基本用法
动态导入语法
使用 ES2015 的动态导入语法 import():
javascript
const routes = [
{
path: '/about',
component: () => import('@/views/About.vue')
}
]原理
import() 返回一个 Promise,Webpack 会将其自动代码分割:
javascript
// 等价于
const About = () => import('@/views/About.vue')
const routes = [
{
path: '/about',
component: About
}
]完整示例
javascript
// router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
const routes = [
{
path: '/',
name: 'Home',
component: () => import('@/views/Home.vue')
},
{
path: '/about',
name: 'About',
component: () => import('@/views/About.vue')
},
{
path: '/user/:id',
name: 'UserDetail',
component: () => import('@/views/user/Detail.vue'),
props: true
},
{
path: '/product',
name: 'ProductList',
component: () => import('@/views/product/List.vue'),
children: [
{
path: ':id',
name: 'ProductDetail',
component: () => import('@/views/product/Detail.vue')
}
]
}
]
const router = new VueRouter({
mode: 'history',
routes
})
export default routerWebpack 分组打包
基本分组
使用 Webpack 的魔法注释 /* webpackChunkName */ 将多个路由打包到同一个 chunk:
javascript
const routes = [
{
path: '/user',
component: () => import(/* webpackChunkName: "user" */ '@/views/user/List.vue')
},
{
path: '/user/:id',
component: () => import(/* webpackChunkName: "user" */ '@/views/user/Detail.vue')
},
{
path: '/user/create',
component: () => import(/* webpackChunkName: "user" */ '@/views/user/Create.vue')
}
]以上三个组件会打包到 user.[hash].js 文件中。
按功能分组
javascript
// 用户相关路由 → user.js
const routes = [
{
path: '/user',
component: () => import(/* webpackChunkName: "user" */ '@/views/user/List.vue')
},
{
path: '/user/:id',
component: () => import(/* webpackChunkName: "user" */ '@/views/user/Detail.vue')
}
]
// 产品相关路由 → product.js
const routes = [
{
path: '/product',
component: () => import(/* webpackChunkName: "product" */ '@/views/product/List.vue')
},
{
path: '/product/:id',
component: () => import(/* webpackChunkName: "product" */ '@/views/product/Detail.vue')
}
]
// 管理后台 → admin.js
const routes = [
{
path: '/admin',
component: () => import(/* webpackChunkName: "admin" */ '@/views/admin/Layout.vue'),
children: [
{
path: 'user',
component: () => import(/* webpackChunkName: "admin" */ '@/views/admin/User.vue')
},
{
path: 'product',
component: () => import(/* webpackChunkName: "admin" */ '@/views/admin/Product.vue')
}
]
}
]分组策略
code
打包结果:
├── app.js # 主应用代码
├── user.js # 用户模块
├── product.js # 产品模块
├── admin.js # 管理后台
└── vendor.js # 第三方库预加载
Prefetch
使用 /* webpackPrefetch: true */ 预加载资源,在浏览器空闲时加载:
javascript
const routes = [
{
path: '/about',
component: () => import(
/* webpackChunkName: "about" */
/* webpackPrefetch: true */
'@/views/About.vue'
)
}
]Prefetch 与懒加载的区别
| 特性 | 懒加载 | Prefetch |
|---|---|---|
| 加载时机 | 路由激活时 | 浏览器空闲时 |
| 首屏影响 | 无 | 轻微增加网络请求 |
| 切换速度 | 需要加载 | 已缓存,瞬间切换 |
| 适用场景 | 非核心页面 | 可能访问的页面 |
预加载策略
javascript
// 首页立即加载(不使用懒加载)
import Home from '@/views/Home.vue'
// 常用页面预加载
const routes = [
{
path: '/',
component: Home
},
{
path: '/about',
component: () => import(
/* webpackChunkName: "about" */
/* webpackPrefetch: true */
'@/views/About.vue'
)
},
{
path: '/contact',
component: () => import(
/* webpackChunkName: "contact" */
/* webpackPrefetch: true */
'@/views/Contact.vue'
)
},
// 低频页面懒加载,不预加载
{
path: '/settings',
component: () => import('@/views/Settings.vue')
}
]加载状态处理
加载进度条
使用 NProgress 显示加载进度:
bash
npm install nprogress
npm install @types/nprogress -Djavascript
// router/index.js
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
NProgress.configure({ showSpinner: false })
router.beforeEach((to, from, next) => {
NProgress.start()
next()
})
router.afterEach(() => {
NProgress.done()
})加载组件
javascript
const routes = [
{
path: '/about',
component: () => ({
component: import('@/views/About.vue'),
loading: LoadingComponent, // 加载时显示的组件
error: ErrorComponent, // 加载失败时显示的组件
delay: 200, // 延迟显示 loading 的时间
timeout: 10000 // 超时时间
})
}
]自定义加载组件
Vue SFC
<!-- components/Loading.vue -->
<template>
<div class="loading-container">
<div class="loading-spinner"></div>
<p>加载中...</p>
</div>
</template>
<style scoped>
.loading-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 200px;
}
.loading-spinner {
width: 40px;
height: 40px;
border: 3px solid #f3f3f3;
border-top: 3px solid #42b983;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>javascript
// router/index.js
import Loading from '@/components/Loading.vue'
import Error from '@/components/Error.vue'
const routes = [
{
path: '/about',
component: () => ({
component: import('@/views/About.vue'),
loading: Loading,
error: Error,
delay: 200,
timeout: 30000
})
}
]实战案例
案例 1:大型项目路由组织
code
views/
├── home/
│ ├── Index.vue
│ └── components/
├── user/
│ ├── List.vue
│ ├── Detail.vue
│ ├── Create.vue
│ └── Edit.vue
├── product/
│ ├── List.vue
│ ├── Detail.vue
│ └── components/
└── admin/
├── Layout.vue
├── Dashboard.vue
└── Settings.vuejavascript
// router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
// 路由模块化
const routes = [
// 首页(直接加载)
{
path: '/',
name: 'Home',
component: () => import('@/views/home/Index.vue')
},
// 用户模块(分组加载)
{
path: '/user',
component: () => import(/* webpackChunkName: "user" */ '@/views/user/List.vue'),
children: [
{
path: ':id',
component: () => import(/* webpackChunkName: "user" */ '@/views/user/Detail.vue')
}
]
},
// 产品模块(分组加载)
{
path: '/product',
component: () => import(/* webpackChunkName: "product" */ '@/views/product/List.vue')
},
// 管理后台(独立分组)
{
path: '/admin',
component: () => import(/* webpackChunkName: "admin" */ '@/views/admin/Layout.vue'),
children: [
{
path: '',
component: () => import(/* webpackChunkName: "admin" */ '@/views/admin/Dashboard.vue')
},
{
path: 'settings',
component: () => import(/* webpackChunkName: "admin" */ '@/views/admin/Settings.vue')
}
]
}
]
const router = new VueRouter({
mode: 'history',
routes
})
export default router案例 2:按需加载第三方库
javascript
// 懒加载 Vue 编辑器组件
const routes = [
{
path: '/editor',
component: () => import(
/* webpackChunkName: "editor" */
'@/views/Editor.vue'
)
}
]
// Editor.vue
export default {
components: {
// 懒加载大型组件
VueEditor: () => import('vue2-editor').then(m => m.VueEditor)
}
}案例 3:权限路由动态加载
javascript
// router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import store from '@/store'
Vue.use(VueRouter)
// 静态路由
export const constantRoutes = [
{
path: '/login',
component: () => import('@/views/Login.vue'),
hidden: true
},
{
path: '/404',
component: () => import('@/views/404.vue'),
hidden: true
}
]
// 动态路由(根据权限加载)
export const asyncRoutes = [
{
path: '/',
component: () => import('@/layouts/DefaultLayout.vue'),
children: [
{
path: '',
name: 'Dashboard',
component: () => import('@/views/Dashboard.vue')
}
]
},
{
path: '/admin',
component: () => import('@/layouts/AdminLayout.vue'),
meta: { roles: ['admin'] },
children: [
{
path: 'user',
name: 'UserManage',
component: () => import(/* webpackChunkName: "admin" */ '@/views/admin/User.vue')
}
]
},
// 404 必须放在最后
{ path: '*', redirect: '/404', hidden: true }
]
const router = new VueRouter({
mode: 'history',
routes: constantRoutes
})
// 动态添加路由
export function addRoutes(routes) {
routes.forEach(route => {
router.addRoute(route)
})
}
export default router性能优化
1. 合理分组
javascript
// ❌ 不推荐:每个路由单独打包
const routes = [
{ path: '/user', component: () => import('@/views/user/List.vue') },
{ path: '/user/:id', component: () => import('@/views/user/Detail.vue') },
{ path: '/user/create', component: () => import('@/views/user/Create.vue') }
]
// 结果:user.List.[hash].js, user.Detail.[hash].js, user.Create.[hash].js
// ✅ 推荐:相关路由合并打包
const routes = [
{
path: '/user',
component: () => import(/* webpackChunkName: "user" */ '@/views/user/List.vue')
},
{
path: '/user/:id',
component: () => import(/* webpackChunkName: "user" */ '@/views/user/Detail.vue')
},
{
path: '/user/create',
component: () => import(/* webpackChunkName: "user" */ '@/views/user/Create.vue')
}
]
// 结果:user.[hash].js2. 首屏关键路由
javascript
// ✅ 推荐:首屏关键路由直接加载
import Home from '@/views/Home.vue'
const routes = [
{
path: '/',
component: Home // 首页直接加载
},
{
path: '/about',
component: () => import('@/views/About.vue') // 其他页面懒加载
}
]3. 预加载高频页面
javascript
const routes = [
// 首页
{
path: '/',
component: () => import('@/views/Home.vue')
},
// 高频页面预加载
{
path: '/product',
component: () => import(
/* webpackChunkName: "product" */
/* webpackPrefetch: true */
'@/views/product/List.vue'
)
},
// 低频页面懒加载
{
path: '/settings',
component: () => import('@/views/Settings.vue')
}
]4. 代码分割策略
javascript
// webpack.config.js 或 vue.config.js
module.exports = {
configureWebpack: {
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
libs: {
name: 'chunk-libs',
test: /[\\/]node_modules[\\/]/,
priority: 10,
chunks: 'initial'
},
elementUI: {
name: 'chunk-elementUI',
priority: 20,
test: /[\\/]node_modules[\\/]_?element-ui(.*)/
},
commons: {
name: 'chunk-commons',
test: resolve('src/components'),
minChunks: 3,
priority: 5,
reuseExistingChunk: true
}
}
}
}
}
}最佳实践
1. 统一懒加载函数
javascript
// utils/lazy-load.js
export function lazyLoad(view) {
return () => import(`@/views/${view}.vue`)
}
// 使用
const routes = [
{
path: '/about',
component: lazyLoad('About')
},
{
path: '/user/:id',
component: lazyLoad('user/Detail')
}
]2. 带分组的懒加载
javascript
// utils/lazy-load.js
export function lazyLoad(view, chunkName) {
return () => import(
/* webpackChunkName: "${chunkName}" */
`@/views/${view}.vue`
)
}
// 使用
const routes = [
{
path: '/user',
component: lazyLoad('user/List', 'user')
},
{
path: '/user/:id',
component: lazyLoad('user/Detail', 'user')
}
]3. 带错误处理的懒加载
javascript
// utils/lazy-load.js
export function lazyLoad(view) {
return () => import(`@/views/${view}.vue`).catch(() => {
// 加载失败时返回错误组件
return import('@/views/Error.vue')
})
}4. 开发环境禁用懒加载
javascript
// utils/lazy-load.js
const isProduction = process.env.NODE_ENV === 'production'
export function lazyLoad(view) {
if (isProduction) {
return () => import(`@/views/${view}.vue`)
} else {
// 开发环境直接加载,提高热更新速度
return require(`@/views/${view}.vue`).default
}
}常见问题
1. 懒加载导致样式闪烁?
确保样式在组件内部使用 scoped,或使用 CSS Modules:
Vue SFC
<style scoped>
/* 组件样式 */
</style>2. 如何查看打包结果?
使用 Webpack Bundle Analyzer:
bash
npm install webpack-bundle-analyzer -Djavascript
// vue.config.js
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
module.exports = {
configureWebpack: {
plugins: [
new BundleAnalyzerPlugin()
]
}
}3. 如何处理动态 import 的 TypeScript 类型?
typescript
// 声明类型
type Component = () => Promise<typeof import('*.vue').default>
const routes: Array<RouteConfig> = [
{
path: '/about',
component: (): Component => import('@/views/About.vue')
}
]4. 懒加载组件如何命名?
使用 webpackChunkName 注释:
javascript
component: () => import(
/* webpackChunkName: "user" */
'@/views/user/Detail.vue'
)
// 生成文件: user.[contenthash].js调试技巧
查看加载的 chunk
javascript
// 在浏览器控制台
console.log(__webpack_modules__)监控 chunk 加载
javascript
router.beforeEach((to, from, next) => {
const startTime = Date.now()
router.app.$once('hook:mounted', () => {
const loadTime = Date.now() - startTime
console.log(`路由 ${to.path} 加载耗时: ${loadTime}ms`)
})
next()
})