Vue Router路由懒加载与导航守卫的搭配实践

阿乐
阿乐 管理员 黑卡会员
发布于 2026-09-10 19:45 ·3 浏览 ·0 回复

路由懒加载和导航守卫,几乎是每个 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` 预加载所有路由,反而浪费带宽,建议只预加载高频路径。

总结

懒加载负责“按需下载”,导航守卫负责“按规则放行”。两者搭配的核心是理解异步导航时序:守卫先于组件加载,动态路由注册后要重新导航,加载过程要有进度反馈,失败要有兜底。把这些细节处理好,才能同时兼顾性能与权限控制,而不是让路由系统在复杂业务里变成新的故障源。

本文转载自 阿乐技术社区,原文地址:https://www.leleweb.cn/thread-214.html
转载请注明出处,版权归原作者所有。

全部回复 0

还没有回复,来抢沙发~