路由 守卫

文章目录

  • 前言
  • 一、守卫类型概览
    • [1.1 三种守卫](#1.1 三种守卫)
    • [1.2 导航完整流程](#1.2 导航完整流程)
  • 二、全局守卫
    • [2.1 beforeEach:全局前置守卫](#2.1 beforeEach:全局前置守卫)
    • [2.2 afterEach:全局后置守卫](#2.2 afterEach:全局后置守卫)
    • [2.3 to / from 路由对象](#2.3 to / from 路由对象)
  • [三、Vue Router 4 导航控制](#三、Vue Router 4 导航控制)
    • [3.1 return 替代 next()](#3.1 return 替代 next())
    • [3.2 return 值的含义](#3.2 return 值的含义)
    • [3.3 异步守卫](#3.3 异步守卫)
  • 四、路由独享守卫
    • [4.1 beforeEnter](#4.1 beforeEnter)
    • [4.2 与全局守卫的分工](#4.2 与全局守卫的分工)
  • 五、组件内守卫
    • [5.1 三个 Composition API 守卫](#5.1 三个 Composition API 守卫)
    • [5.2 onBeforeRouteLeave:离开确认](#5.2 onBeforeRouteLeave:离开确认)
    • [5.3 onBeforeRouteUpdate:同组件参数变化](#5.3 onBeforeRouteUpdate:同组件参数变化)
    • [5.4 onBeforeRouteEnter:组件未创建](#5.4 onBeforeRouteEnter:组件未创建)
  • 六、典型场景
    • [6.1 登录拦截 + 回跳](#6.1 登录拦截 + 回跳)
    • [6.2 动态页面标题](#6.2 动态页面标题)
    • [6.3 NProgress 进度条](#6.3 NProgress 进度条)
    • [6.4 编辑页离开确认(组合式封装)](#6.4 编辑页离开确认(组合式封装))
  • 七、面试聚焦
    • [7.1 Vue Router 4 推荐 return 替代 next()](#7.1 Vue Router 4 推荐 return 替代 next())
    • [7.2 onBeforeRouteEnter 能访问组件实例吗?](#7.2 onBeforeRouteEnter 能访问组件实例吗?)
    • [7.3 多个守卫的执行顺序](#7.3 多个守卫的执行顺序)
  • 八、易混淆点
  • 九、思考与练习
  • 总结

前言

路由守卫是 Vue Router 在导航跳转各阶段提供的拦截机制,常用于登录校验、权限控制、离开确认等场景。本篇会讲清楚:

  • 全局守卫、路由独享守卫、组件内守卫
  • Vue Router 4 用 return 控制导航
  • 常见业务场景与完整示例

一、守卫类型概览

1.1 三种守卫

类型 注册位置 作用范围
全局守卫 router.beforeEach / afterEach 所有路由跳转
路由独享守卫 路由配置 beforeEnter 单个路由
组件内守卫 组件内 onBeforeRouteXxx 当前组件相关路由

1.2 导航完整流程

复制代码
触发导航
  ↓
beforeRouteLeave(离开的旧组件)
  ↓
beforeEach(全局前置)
  ↓
beforeEnter(目标路由独享)
  ↓
beforeRouteEnter(进入的新组件)
  ↓
解析异步组件 / 执行路由组件守卫
  ↓
beforeRouteUpdate(同一组件,路由参数变化)
  ↓
导航确认
  ↓
afterEach(全局后置)
  ↓
DOM 更新

二、全局守卫

2.1 beforeEach:全局前置守卫

javascript 复制代码
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'

const router = createRouter({
  history: createWebHistory(),
  routes: [
    { path: '/login', component: () => import('@/views/Login.vue') },
    {
      path: '/admin',
      component: () => import('@/views/Admin.vue'),
      meta: { auth: true }
    }
  ]
})

router.beforeEach((to, from) => {
  const token = localStorage.getItem('token')

  // 需要登录但未登录 → 重定向
  if (to.meta.auth && !token) {
    return { path: '/login', query: { redirect: to.fullPath } }
  }
})

2.2 afterEach:全局后置守卫

javascript 复制代码
router.afterEach((to, from) => {
  // 设置页面标题
  document.title = to.meta.title || '默认标题'

  // 页面访问埋点
  trackPageView(to.path)
})

注意afterEach 不接受 return 值,无法阻止或重定向导航,适合做副作用(标题、埋点、进度条结束)。

2.3 to / from 路由对象

javascript 复制代码
router.beforeEach((to, from) => {
  console.log(to.path)     // '/user/1'
  console.log(to.params)   // { id: '1' }
  console.log(to.query)    // { tab: 'profile' }
  console.log(to.meta)     // { auth: true, title: '用户' }
  console.log(to.fullPath) // '/user/1?tab=profile'
  console.log(to.name)     // 'User'
})

三、Vue Router 4 导航控制

3.1 return 替代 next()

Vue Router 3 使用 next() 回调,Vue Router 4 推荐通过 返回值 控制导航:

javascript 复制代码
// ❌ Vue Router 3
router.beforeEach((to, from, next) => {
  if (!token) next('/login')
  else next()
})

// ✅ Vue Router 4
router.beforeEach((to, from) => {
  if (!token) return '/login'           // 重定向
  // return false                        // 取消导航
  // return { name: 'Login' }            // 命名路由重定向
  // return undefined / true             // 放行(默认)
})

3.2 return 值的含义

返回值 效果
undefined / true 放行,继续导航
false 取消当前导航
路由路径字符串 重定向到该路径
路由位置对象 { path, name, params, query } 重定向到指定位置

3.3 异步守卫

javascript 复制代码
router.beforeEach(async (to, from) => {
  if (to.meta.auth) {
    const isValid = await checkToken()
    if (!isValid) return '/login'
  }
})

守卫可以返回 Promise,Vue Router 会等待 Promise resolve 后再继续导航。


四、路由独享守卫

4.1 beforeEnter

仅对当前路由生效,在 beforeEach 之后、beforeRouteEnter 之前执行。

javascript 复制代码
const routes = [
  {
    path: '/admin',
    component: () => import('@/views/Admin.vue'),
    meta: { roles: ['admin'] },
    beforeEnter: (to, from) => {
      const role = localStorage.getItem('role')
      if (!to.meta.roles.includes(role)) {
        return '/403'
      }
    }
  },
  {
    path: '/user/:id',
    component: () => import('@/views/User.vue'),
    beforeEnter: (to, from) => {
      // 校验 id 格式
      if (!/^\d+$/.test(to.params.id)) {
        return '/404'
      }
    }
  }
]

4.2 与全局守卫的分工

javascript 复制代码
// 全局:登录态校验(所有需 auth 的路由)
router.beforeEach((to, from) => {
  if (to.meta.auth && !token) return '/login'
})

// 独享:特定路由的角色/参数校验
{
  path: '/admin',
  beforeEnter: (to, from) => {
    if (role !== 'admin') return '/403'
  }
}

五、组件内守卫

5.1 三个 Composition API 守卫

javascript 复制代码
import {
  onBeforeRouteEnter,
  onBeforeRouteUpdate,
  onBeforeRouteLeave
} from 'vue-router'
守卫 触发时机
onBeforeRouteEnter 进入该组件对应路由前(组件尚未创建)
onBeforeRouteUpdate 路由变化但复用同一组件时(如 /user/1/user/2
onBeforeRouteLeave 离开该组件对应路由前

5.2 onBeforeRouteLeave:离开确认

vue 复制代码
<script setup>
import { ref } from 'vue'
import { onBeforeRouteLeave } from 'vue-router'

const isDirty = ref(false)

onBeforeRouteLeave((to, from) => {
  if (isDirty.value) {
    const answer = window.confirm('有未保存的修改,确定离开?')
    if (!answer) return false  // 取消导航
  }
})
</script>

5.3 onBeforeRouteUpdate:同组件参数变化

vue 复制代码
<script setup>
import { onBeforeRouteUpdate } from 'vue-router'

const fetchUser = async (id) => { /* ... */ }

onBeforeRouteUpdate(async (to, from) => {
  // /user/1 → /user/2,组件复用,需重新拉数据
  await fetchUser(to.params.id)
})
</script>

也可用 watch(() => route.params.id, fetchUser) 替代,效果类似。

5.4 onBeforeRouteEnter:组件未创建

vue 复制代码
<script setup>
import { onBeforeRouteEnter } from 'vue-router'

onBeforeRouteEnter((to, from) => {
  // ❌ 无法访问组件实例(组件尚未创建)
  // ❌ 无法使用 setup 中定义的 ref / 方法

  // ✅ 可在进入前做数据预取
  // return fetchUser(to.params.id)  // 返回 Promise,导航会等待
})
</script>

Vue Router 4 变化 :不再使用 next(vm => ...) 回调访问实例。进入后需在 onMounted 或通过 watch route 处理组件内逻辑。


六、典型场景

6.1 登录拦截 + 回跳

javascript 复制代码
router.beforeEach((to, from) => {
  const token = localStorage.getItem('token')

  if (to.path === '/login') return true

  if (to.meta.auth && !token) {
    return { path: '/login', query: { redirect: to.fullPath } }
  }
})
javascript 复制代码
// Login.vue 登录成功后
const route = useRoute()
const router = useRouter()
router.push(route.query.redirect || '/')

6.2 动态页面标题

javascript 复制代码
router.afterEach((to) => {
  document.title = to.meta.title
    ? `${to.meta.title} - 我的应用`
    : '我的应用'
})

6.3 NProgress 进度条

javascript 复制代码
import NProgress from 'nprogress'

router.beforeEach(() => {
  NProgress.start()
})

router.afterEach(() => {
  NProgress.done()
})

6.4 编辑页离开确认(组合式封装)

javascript 复制代码
// composables/useLeaveConfirm.js
import { ref } from 'vue'
import { onBeforeRouteLeave } from 'vue-router'

export function useLeaveConfirm() {
  const isDirty = ref(false)

  onBeforeRouteLeave(() => {
    if (isDirty.value) {
      return window.confirm('有未保存的修改,确定离开?')
    }
  })

  return { isDirty }
}

七、面试聚焦

7.1 Vue Router 4 推荐 return 替代 next()

javascript 复制代码
// VR4:return '/login' 重定向,return false 取消
router.beforeEach((to, from) => {
  if (!token) return '/login'
})

7.2 onBeforeRouteEnter 能访问组件实例吗?

不能 。组件尚未创建,无法访问 setup 中的 ref、methods。数据预取可返回 Promise;进入后的逻辑放在 onMountedwatch route

7.3 多个守卫的执行顺序

离开旧组件 → 全局 beforeEach → 路由 beforeEnter → 进入新组件 beforeRouteEnter → beforeRouteUpdate → 全局 afterEach。

任一守卫 return false 或重定向,后续守卫按规则中断或转向新目标。


八、易混淆点

  1. afterEach 不能阻止导航 :只做副作用,无 return 控制。
  2. onBeforeRouteEnter 无组件实例 :Vue Router 4 已移除 next(vm => ...),勿与 VR3 混用。
  3. 全局守卫按注册顺序执行 :多个 beforeEach 依次执行。
  4. beforeRouteUpdate vs watch route:同组件参数变化时两者都可用;守卫在导航确认前执行,适合拦截;watch 适合响应式更新。
  5. meta 需主动配置 :守卫里 to.meta.auth 要在路由配置中预先声明。

九、思考与练习

1. 路由守卫有哪些类型?

解析:全局守卫(beforeEach / afterEach)、路由独享守卫(beforeEnter)、组件内守卫(onBeforeRouteEnter / Update / Leave)。

2. Vue Router 4 如何取消或重定向导航?

解析:return false 取消;return '/path'return { name: 'Xxx' } 重定向;不 return 或 return true 放行。

3. onBeforeRouteEnter 中为什么不能访问组件实例?

解析:守卫执行时组件尚未创建,setup 尚未运行,因此无法访问 ref、methods 等。

4. beforeEach 和 beforeEnter 如何分工?

解析:beforeEach 做全局通用逻辑(如登录态);beforeEnter 做单个路由特有校验(如角色权限、参数格式)。

5. 如何实现编辑页离开时的未保存提示?

javascript 复制代码
onBeforeRouteLeave(() => {
  if (isDirty.value && !confirm('确定离开?')) return false
})

总结

  • 三种守卫:全局、路由独享、组件内,在导航流程不同阶段拦截
  • Vue Router 4 :用 return 控制导航,替代 Vue Router 3 的 next()
  • 全局前置 :登录校验、权限拦截;全局后置:标题、埋点、进度条
  • 组件内:Leave 做离开确认,Update 处理同组件参数变化,Enter 无法访问实例
  • 执行顺序:离开 → beforeEach → beforeEnter → Enter → Update → afterEach