Vue Router路由懒加载与导航守卫的搭配实践
路由懒加载和导航守卫,几乎是每个 Vue 项目都会用到的两个能力。前者解决首屏体积和按需加载,后者负责登录校验、权限控制、动态路由等导航逻辑。单独用都不难,但一旦搭配起来,就容易遇到“白屏”“无限重定向”“守卫里拿不到组件”“动态路由不生效”等问题。核心原因通常不是 API 不会用,而是没有理清异步导航的时序。
这篇文章结合常见后台管理场景,聊聊 Vue Router 中懒加载与导航守卫的搭配实践。默认以 Vue 3 + Vue Router 4 为例,思路同样适用于 Vue 2 + Vue Router 3。
基础写法:懒加载不是“延迟加载”
路由懒加载通常写成动态 import:
const routes = [
{ path: '/', component: () => import('../views/Home.vue') },
{
path: '/about',
component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
},
{
path: '/admin',
component: () => import('../views/Admin.vue'),
meta: { requiresAuth: true, roles: ['admin'] }
}
]
它返回的是 Promise,Vue Router 会在导航过程中等待这个 Promise 完成。也就是说,懒加载组件并不是在点击时才“同步”加载,而是被纳入了一次异步导航流程。导航守卫同样支持异步,两者叠加后,导航会经历“守卫执行 -> 异步组件解析 -> 导航确认”的过程。
一个常见误区是在 `beforeEach` 里试图访问组件实例,比如 `to.matched[0].components.default`,拿到的其实是 `() => import(...)` 这个函数,不是组件对象。守卫阶段能依赖的主要是路由记录上的 `meta`、`params`、`query` 以及外部状态。组件实例要等组件真正加载和创建之后才能访问。
时序:守卫先于懒加载组件
把导航守卫的执行顺序记清楚,很多问题会自然消失:
1. 全局 `beforeEach`
2. 路由独享 `beforeEnter`
3. 解析异步路由组件
4. 被激活组件的 `beforeRouteEnter`
5. 全局 `beforeResolve`
6. 导航确认
7. 全局 `afterEach`
这个顺序意味着:`beforeEach` 适合做登录判断、权限预检、动态路由注册;`beforeResolve` 适合在所有异步组件和守卫都已就绪后做最后确认;`beforeRouteEnter` 虽然不能访问 `this`,但可以通过 `next(vm => {})` 拿到组件实例。懒加载组件加载失败时,导航会直接中断,所以还需要错误兜底。
动态权限路由:addRoute 后要重新导航
后台系统最常见的组合是:登录后拉取用户信息,根据角色过滤异步路由,再用 `router.addRoute` 动态注册。由于这些路由通常也是懒加载的,守卫里的处理要格外注意:
router.beforeEach(async (to, from, next) => {
if (!store.state.user.token) {
return next({ name: 'Login', query: { redirect: to.fullPath } })
}
if (!store.state.user.loaded) {
const user = await store.dispatch('fetchUser')
const allowedRoutes = filterAsyncRoutes(asyncRoutes, user.roles)
allowedRoutes.forEach(route => router.addRoute(route))
store.commit('setLoaded', true)
// 关键:重新进入当前导航,让新路由表生效
next({ ...to, replace: true })
} else {
next()
}
})
这里有两个关键点。第一,`addRoute` 之后当前导航仍然按照旧路由表匹配,必须用 `next({ ...to, replace: true })` 或 `next(to.fullPath)` 重新触发一次导航。第二,必须用 `loaded` 这类标志位防止无限循环,否则每次重新导航都会再次进入拉取用户信息的逻辑。
如果动态路由本身是懒加载的,也不需要手动 `await import()`。让 `addRoute` 接收懒加载函数即可,Vue Router 会在真正导航到该路由时再解析组件。
加载反馈与错误兜底
懒加载带来的网络延迟是真实存在的。用户点击菜单后如果界面没有任何变化,很容易以为系统卡死。最省事的做法是接入 NProgress:
router.beforeEach((to, from, next) => {
NProgress.start()
next()
})
router.afterEach(() => {
NProgress.done()
})
router.onError(() => {
NProgress.done()
})
`router.onError` 可以捕获动态导入失败,比如分包请求 404、网络中断。对于弱网环境,可以做一个有限次数的重试:
const retryImport = (importFn, times = 2) => {
return () => importFn().catch(err => {
if (times <= 0) throw err
return new Promise(resolve => {
setTimeout(() => resolve(retryImport(importFn, times - 1)()), 1000)
})
})
}
const Admin = retryImport(() => import('../views/Admin.vue'))
注意别无限重试,否则网络彻底不可用时只会让用户等更久。失败后给出“加载失败,请刷新重试”的提示,往往比默默重试更友好。
几个容易踩的坑
- 守卫中 `next` 没有调用,或者一个分支里调用了多次,导致导航挂起或报错。
- 在 `beforeEach` 里用 `to.matched` 访问组件定义,误把懒加载函数当组件。
- 动态路由添加后刷新页面丢失,需要在应用初始化时根据 token 重新拉取并注册。
- 懒加载组件内部使用 `beforeRouteEnter` 时,`next(vm => {})` 的执行时机会更晚,但 Vue Router 会等待组件解析完成。
- 滥用 `webpackPrefetch` 预加载所有路由,反而浪费带宽,建议只预加载高频路径。
总结
懒加载负责“按需下载”,导航守卫负责“按规则放行”。两者搭配的核心是理解异步导航时序:守卫先于组件加载,动态路由注册后要重新导航,加载过程要有进度反馈,失败要有兜底。把这些细节处理好,才能同时兼顾性能与权限控制,而不是让路由系统在复杂业务里变成新的故障源。
转载请注明出处,版权归原作者所有。
管理员
黑卡会员