【Vue Router 路由守卫(Navigation Guards)指南:概念、执行顺序、beforeResolve、异步路由组件】

Vue Router 路由守卫(Navigation Guards)指南:概念、执行顺序、beforeResolve、异步路由组件

目标读者:有 Vue2/Vue3 项目经验、希望系统梳理 Vue Router 路由与守卫机制,能在实际工程协作与技术交流中解释清楚"为什么这样设计、怎么落地、有哪些边界与坑"。


目录

  • [1. 路由与导航的基本概念](#1. 路由与导航的基本概念)
  • [2. 路由守卫是什么:它解决了什么问题](#2. 路由守卫是什么:它解决了什么问题)
  • [3. 守卫分类:为什么分全局、路由独享、组件内](#3. 守卫分类:为什么分全局、路由独享、组件内)
  • [4. 固定导航流水线:执行顺序与"看起来不同"的原因](#4. 固定导航流水线:执行顺序与“看起来不同”的原因)
  • [5. beforeEach / beforeEnter / beforeResolve / afterEach:分别用来干什么](#5. beforeEach / beforeEnter / beforeResolve / afterEach:分别用来干什么)
  • [6. 异步路由组件是什么:为什么影响导航阶段](#6. 异步路由组件是什么:为什么影响导航阶段)
  • [7. 典型案例与实践指南](#7. 典型案例与实践指南)
  • [8. Vue Router 3 vs 4:API 差异与迁移注意点](#8. Vue Router 3 vs 4:API 差异与迁移注意点)
  • [9. 常见坑点与排查清单](#9. 常见坑点与排查清单)
  • [10. 记忆化总结(要点速记版)](#10. 记忆化总结(要点速记版))
  • 术语表

1. 路由与导航的基本概念

1.1 路由(Route)是什么

  • 路由是一种 URL → 页面/组件状态 的映射规则。
  • 在 SPA 中,路由变化通常不会触发整页刷新,而是改变:
    • 当前要渲染的组件树
    • 组件接收的参数(params / query
    • 与页面相关的副作用(标题、埋点、数据加载等)

1.2 导航(Navigation)是什么

  • 导航是从一个路由状态 from 切换到另一个路由状态 to 的过程。
  • 导航不是"单纯切换组件",而是一次可控的状态迁移:
    • 可能需要鉴权
    • 可能需要异步准备(加载路由组件/获取数据)
    • 可能需要阻止离开(未保存提示)

2. 路由守卫是什么:它解决了什么问题

2.1 定义

路由守卫(Navigation Guards)本质是:

  • 在一次导航的生命周期中插入 可拦截、可异步、可重定向 的钩子函数。
  • 它让你能在"导航发生前/中/后"执行控制逻辑。

2.2 为什么需要守卫(工程视角)

如果没有守卫,常见问题是:

  • 只能在组件 mounted 里做鉴权 → 会闪屏(页面先渲染再被踢走)
  • 鉴权、动态路由注入逻辑分散在各页面 → 重复、难维护
  • 导航过程涉及异步逻辑(权限、配置、远程菜单)→ 需要 可等待 的机制
  • 快速连续跳转会产生竞态 → 需要 可取消/可重入 的导航控制

3. 守卫分类:为什么分全局、路由独享、组件内

可以用"作用域(Scope)"来理解。

3.1 三类守卫分别是什么

分类 作用域 典型 API 适用问题 关键特点
全局守卫(Global) 全站 beforeEach / beforeResolve / afterEach 全站统一规则:鉴权、动态路由、埋点、进度条 一次配置,处处生效
路由独享守卫(Per-route) 某条路由记录 beforeEnter 某模块门禁:只对 admin/finance 等模块 跟着路由表走,集中管理
组件内守卫(In-component) 某个组件实例 beforeRouteLeave/Update/Enter 与页面状态强相关:未保存离开、同组件复用刷新 就近维护,贴近状态

3.2 为什么要这么区分(高分解释)

  • 责任边界清晰:应用策略、路由规则、页面状态各管各的
  • 复用成本最小化:能全局复用的不要散落到组件里
  • 降低耦合:路由层不依赖具体组件细节,组件也不承载全站策略
  • 执行顺序可控:先出门(leave),再过总闸(global),再过分闸(route),最后进屋(enter)

3.3 记忆口诀(作用域)

  • 全站 → 这条路 → 这个页面

4. 固定导航流水线:执行顺序与"看起来不同"的原因

重点:顺序不是"多种情况",而是一条固定流水线在不同导航里有些步骤为空。

4.1 固定导航流水线(快速复述版)

一次导航从 fromto,按照以下阶段执行(按语义理解即可):

  1. 离开阶段(Leave)
    • beforeRouteLeave(离开的组件里有才触发)
  2. 全局前置(Global before)
    • router.beforeEach
  3. 复用更新(Update)
    • beforeRouteUpdate(同组件复用、参数变化才触发)
  4. 路由门禁(Route before)
    • beforeEnter(目标路由有才触发)
  5. 解析阶段(Resolve / Components)
    • 解析匹配组件、加载异步路由组件(() => import()
    • 运行 beforeRouteEnter(进入组件里有才触发)
  6. 全局解析后(Global resolve)
    • router.beforeResolve
  7. 导航完成(After)
    • router.afterEach

记忆化:先出门 → 过总闸 → 同屋更新 → 过分闸 → 拿钥匙进屋 → 总检 → 收尾

4.2 为什么有时看起来"顺序不同"

因为某些阶段根本不存在:

  • 没有离开确认 → 没有 beforeRouteLeave
  • 不是同组件复用 → 没有 beforeRouteUpdate
  • 目标路由没写 beforeEnter → 跳过
  • 目标路由组件不是异步 import → "解析阶段"很快,感觉不到
  • 组件没写 beforeRouteEnter → 进入阶段跳过

4.3 为什么是 "beforeRouteLeave → beforeEach"

  • 语义:先问"旧页面放不放你走",再谈"全站策略让不让你去"
  • 避免副作用:如果 beforeEach 里会请求/埋点/开启 loading,用户最终取消离开会产生脏副作用
  • 体验:离开确认应该立即响应,不应等全局异步逻辑跑完

4.4 为什么是 "beforeEach → beforeRouteUpdate → beforeEnter"

  • beforeEach 是总闸:先决定本次导航是否继续,避免无意义的实例级工作
  • beforeRouteUpdate 依赖"匹配结果 + 复用判定 + 已存在组件实例",因此在全局通过后收集执行
  • beforeEnter 是目标路由记录门禁:在组件进入前保证复用组件已同步到新参数,避免状态交错

5. beforeEach / beforeEnter / beforeResolve / afterEach:分别用来干什么

5.1 beforeEach:全站总闸门

职责:对每一次导航执行统一前置逻辑,决定放行/取消/改道。

常见用途:

  • 登录鉴权(未登录 → /login
  • 动态路由注入(拉权限 → addRoute → 重新进入)
  • 全局进度条开始(如 NProgress.start()

5.2 beforeEnter:某条路由/某模块门禁

职责:只对特定模块/路由生效的门禁。

典型场景:

  • /admin 模块需要管理员权限
  • 进入 /tenant/* 前必须选择租户

对比 beforeEach:

  • beforeEach 适合"全站规则",否则会变成一堆 if-else
  • beforeEnter 适合"模块规则",跟路由配置集中管理

5.3 beforeResolve:最后的全局兜底(总检)

一句话beforeResolve 是导航确认前的最后一道全局前置检查。

它的价值在于:

  • 保证路由级守卫与组件相关解析流程(含异步组件)都已走到"接近 ready"的阶段
  • 适合做最终兜底:
    • 权限/配置最终检查
    • 最终确认 loading 是否应该结束
    • 动态路由注入后的 matched 校验

直觉理解:beforeEach 是"进流程前的总闸",beforeResolve 是"进门前的总检"。

5.4 afterEach:导航完成后的收尾

关键限制afterEach 不能拦截/重定向,只能做副作用。

典型用途:

  • PV 埋点(确认落地路由)
  • 更新标题、面包屑
  • 结束进度条(NProgress.done()

6. 异步路由组件是什么:为什么影响导航阶段

6.1 定义

异步路由组件是指路由组件用函数返回动态 import:

js 复制代码
// Vue Router 3/4 均可
const routes = [
  {
    path: '/report',
    component: () => import('@/pages/Report.vue'),
  },
]

6.2 为什么要用(选型建议)

  • 路由组件通常体积大(图表、地图、编辑器、复杂表格)
  • 异步加载能实现 按路由拆包(code splitting)
  • 目标:减少首包体积、加快首屏

6.3 为什么会影响守卫顺序

导航到 /report 时,必须先把 Report.vue 对应的 chunk 加载并解析,才能进入"渲染新页面"。这就是"解析阶段"的来源。

beforeResolve 的设计目的之一:在异步组件与组件内守卫都处理到位后做最后兜底。


7. 典型案例与实践指南

7.1 案例:登录鉴权 + 白名单(beforeEach)

目标:未登录访问需要登录的页面 → 跳转登录;登录后回跳原页面。

Vue Router 4(推荐 return 风格)
ts 复制代码
// router/guard.ts
router.beforeEach(async (to) => {
  const isPublic = Boolean(to.meta.public)
  const token = auth.getToken()

  if (isPublic) return true

  if (!token) {
    return {
      name: 'login',
      query: { redirect: to.fullPath },
    }
  }

  return true
})
Vue Router 3(next 风格)
js 复制代码
router.beforeEach((to, from, next) => {
  const isPublic = Boolean(to.meta.public)
  const token = auth.getToken()

  if (isPublic) return next()

  if (!token) {
    return next({ name: 'login', query: { redirect: to.fullPath } })
  }

  next()
})

最佳实践

  • beforeEach 里避免多次调用 next()
  • 推荐用 return(Router 4)减少分支错误

7.2 案例:动态路由注入(权限路由)

场景:登录后根据权限生成菜单与路由。第一次进入系统必须先拉权限,再 addRoute。

关键点:

  • 用 flag 避免重复注入
  • 注入完成后重新进入目标路由(通常 next(to.fullPath)return to.fullPath
ts 复制代码
let routesInited = false

router.beforeEach(async (to) => {
  const token = auth.getToken()
  if (!token) return to.name === 'login' ? true : { name: 'login' }

  if (!routesInited) {
    const perms = await api.getPermissions()
    const dynRoutes = buildRoutes(perms)
    dynRoutes.forEach(r => router.addRoute(r))
    routesInited = true

    // 关键:重新进入,确保 matched 重新计算
    return to.fullPath
  }

  return true
})

7.3 案例:离开确认(beforeRouteLeave)

场景:编辑页未保存,离开提示确认。

js 复制代码
export default {
  data() {
    return { dirty: false }
  },
  beforeRouteLeave(to, from, next) {
    if (!this.dirty) return next()
    const ok = window.confirm('内容未保存,确定离开?')
    next(ok)
  },
}

实践建议

  • 离开确认属于页面状态,放组件内最合适
  • 不要放全局 beforeEach(会污染全局逻辑)

7.4 案例:同组件复用刷新(beforeRouteUpdate)

场景:/user/1/user/2 复用同一个详情组件。

js 复制代码
export default {
  async beforeRouteUpdate(to, from, next) {
    await this.fetchDetail(to.params.id)
    next()
  },
}

7.5 案例:路由 PV 埋点 + sendBeacon

结论
  • PV/路由到达事件 :建议放 router.afterEach(拿到最终落地路由,减少中途重定向脏数据)
  • 停留时长/退出上报 :建议放 pagehide / visibilitychange,并使用 navigator.sendBeacon 提高卸载场景成功率
路由 PV(afterEach)
ts 复制代码
router.afterEach((to, from) => {
  analytics.track('page_view', {
    path: to.fullPath,
    referrer: from.fullPath,
  })
})
退出/停留(pagehide + sendBeacon)
ts 复制代码
function reportLeave(payload: any) {
  const url = '/analytics/leave'
  const body = JSON.stringify(payload)

  // sendBeacon 适合卸载场景
  navigator.sendBeacon?.(url, body)
}

window.addEventListener('pagehide', () => {
  reportLeave({
    path: location.pathname,
    ts: Date.now(),
  })
})

document.addEventListener('visibilitychange', () => {
  if (document.visibilityState === 'hidden') {
    reportLeave({
      path: location.pathname,
      ts: Date.now(),
    })
  }
})

最佳实践

  • sendBeacon 不适合做所有请求;更适合"卸载/切后台"这类容易丢包的上报
  • afterEach 里更多是"到达页"事件;离开/停留要用 pagehide/visibilitychange

8. Vue Router 3 vs 4:API 差异与迁移注意点

对比项 Vue Router 3(Vue2) Vue Router 4(Vue3)
控制导航 next() 推荐 return true/false/routeLocation
异步守卫 手动 next() await 更自然
常见坑 next() 多次/漏调 return 更不易写错

8.1 Router 4 的推荐写法(return 风格)

  • return true:放行
  • return false:取消
  • return { name: 'login' }:重定向

9. 常见坑点与排查清单

9.1 导航卡死

  • Vue Router 3:忘记 next() / 分支没返回
  • Vue Router 4:async 分支未 return

9.2 next 调用两次(Router 3)

  • return next(...) 保证单路径

9.3 重定向循环

  • 登录页也被鉴权拦截
  • 解决:白名单 / meta.public

9.4 动态路由注入后 404

  • addRoute 后不重新进入
  • 解决:重新进入 to.fullPath

9.5 组件复用导致数据不刷新

  • mounted 不会重跑
  • beforeRouteUpdate / watch route params

9.6 afterEach 里做鉴权

  • afterEach 不能拦截,只会产生"闪一下再跳"

9.7 sendBeacon 使用误区

  • afterEach 更适合 PV;退出类上报使用 pagehide/visibilitychange

10. 记忆化总结(要点速记版)

10.1 作用域口诀

  • 全站 → 这条路 → 这个页面

10.2 流水线口诀

  • 出门(leave)→ 总闸(beforeEach)→ 同屋更新(beforeRouteUpdate)→ 分闸(beforeEnter)→ 拿钥匙进屋(异步组件/enter)→ 总检(beforeResolve)→ 收尾(afterEach)

10.3 一分钟摘要

  • 路由守卫是导航生命周期中的拦截器链,支持异步、取消、重定向。
  • 按作用域分全局、路由独享、组件内,目的是清晰边界、降低耦合、提高复用。
  • 执行上是一条固定流水线,某些步骤在特定导航里不存在所以看起来不同。
  • beforeEach 做全站门禁,beforeEnter 做模块门禁,beforeResolve 做最后兜底,afterEach 做收尾埋点。
  • 异步路由组件通过 () => import() 实现按路由拆包,会引入解析阶段,因此 beforeResolve 在工程里很重要。

术语表

  • 导航(Navigation) :一次从 fromto 的路由迁移过程。
  • 路由记录(Route Record):路由表中的一条配置(包含 path/component/meta/beforeEnter)。
  • 匹配(matched):当前路由匹配到的路由记录链条(父子嵌套)。
  • 异步路由组件 :路由组件使用 () => import() 动态加载,实现按路由拆包。
  • PV(Page View):页面访问量统计,通常在 afterEach 记录"最终落地页"。
  • sendBeacon:浏览器提供的卸载期上报 API,适合页面离开时保证上报成功率。
相关推荐
一 乐1 天前
婚纱摄影网站|基于ssm + vue婚纱摄影网站系统(源码+数据库+文档)
前端·javascript·数据库·vue.js·spring boot·后端
C_心欲无痕1 天前
ts - tsconfig.json配置讲解
linux·前端·ubuntu·typescript·json
清沫1 天前
Claude Skills:Agent 能力扩展的新范式
前端·ai编程
yinuo1 天前
前端跨页面通信终极指南:方案拆解、对比分析
前端
北辰alk1 天前
Vue 模板引擎深度解析:基于 HTML 的声明式渲染
vue.js
北辰alk1 天前
Vue 自定义指令完全指南:定义与应用场景详解
vue.js
yinuo1 天前
前端跨页面通讯终极指南⑨:IndexedDB 用法全解析
前端
北辰alk1 天前
Vue 动态路由完全指南:定义与参数获取详解
vue.js
北辰alk1 天前
Vue Router 完全指南:作用与组件详解
vue.js
北辰alk1 天前
Vue 中使用 this 的完整指南与注意事项
vue.js