人人都会AI编程

12.5 路由守卫体系:全局守卫、路由独享守卫、组件内守卫

更新时间:2026-07-09

路由守卫的核心作用是在导航发生前后“拦一道”,让你有机会决定“让不让用户进这个页面”或者“离开页面前要不要提醒保存”。Vue Router 提供了三种粒度的守卫,覆盖了从全局到单个路由再到具体组件的控制需求。

全局守卫:把控所有路由的“总闸门”

全局守卫直接注册在 router 实例上,对每一次路由跳转都生效,最适合做登录拦截、页面标题修改、全局埋点这类通用逻辑。

  • beforeEach:进入任何路由前触发,最常用的守卫。
  • beforeResolve:在所有组件内守卫和异步路由组件解析之后触发,使用频率较低。
  • afterEach:导航确认之后触发,不能阻止导航,常用于修改页面标题或上报 PV。
// router/index.js
const router = createRouter({ ... })

router.beforeEach((to, from, next) => {
  // 典型场景:登录拦截
  const token = localStorage.getItem('token')
  
  // 如果要去的页面需要登录,且当前没有 token
  if (to.meta.requiresAuth && !token) {
    // 跳转到登录页,并带上来源路径,登录后可以跳回来
    next({ path: '/login', query: { redirect: to.fullPath } })
  } else {
    next()  // 放行
  }
})

router.afterEach((to) => {
  document.title = to.meta.title || '默认标题'
})

关键点next 函数是守卫放行的唯一出口(除了 afterEach 不需要)。你可以调用 next() 放行、next(false) 中断、next('/path') 强制跳转。在 Vue Router 4 中,也可以返回一个路径字符串或对象来替代 next 调用,更简洁:

router.beforeEach((to) => {
  if (to.meta.requiresAuth && !getToken()) {
    return '/login'  // 相当于 next('/login')
  }
})

路由独享守卫:给特定路由“上把锁”

有些逻辑只需要作用在某一条或某几条路由上,这时可以定义在路由配置的 beforeEnter 中:

const routes = [
  {
    path: '/admin',
    component: () => import('@/views/Admin.vue'),
    meta: { requiresAuth: true, role: 'admin' },
    beforeEnter: (to, from, next) => {
      // 仅 /admin 路由触发
      const userRole = getUserRole()
      if (userRole !== 'admin') {
        next('/403')  // 无权限
      } else {
        next()
      }
    }
  }
]

beforeEnter 的签名与全局 beforeEach 完全一致,但它只负责当前路由。当你在一个复杂应用里,不同业务模块由不同权限控制时,把权限规则写在各自路由配置的 beforeEnter 中,能让代码更内聚,避免全局守卫变成一个巨大的 if-else 地狱。

组件内守卫:在组件级别精确控制

当你需要更细粒度的控制——比如“进入某组件前拉取数据”、“离开某组件前确认是否有未保存内容”——就可以在组件选项或组合式 API 中使用组件内守卫。Vue Router 提供了三个钩子:

| 选项式 API | 组合式 API (vue-router) | 触发时机 |
|-----------|----------------|---------|
| beforeRouteEnter | onBeforeRouteEnter | 进入该组件的路由被确认,此时组件实例尚未创建,无法访问 this。 |
| beforeRouteUpdate | onBeforeRouteUpdate | 当前路由改变但组件被复用时调用(如 /user/1/user/2)。 |
| beforeRouteLeave | onBeforeRouteLeave | 离开当前组件对应的路由时调用,常用于阻止用户意外离开。 |

<script setup>
import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'

// 场景1:表单未保存时阻止离开
onBeforeRouteLeave((to, from, next) => {
  if (formChanged.value) {
    const answer = window.confirm('你有未保存的更改,确定离开吗?')
    if (!answer) return next(false)
  }
  next()
})

// 场景2:路由参数变化时重新请求数据
onBeforeRouteUpdate(async (to, from, next) => {
  await fetchUserData(to.params.id)
  next()
})
</script>

三个注意点

  1. beforeRouteEnter 中拿不到组件实例:因为组件还没创建。如果确实需要在导航确认后操作组件,可以通过 next 的回调参数延迟访问:
   beforeRouteEnter(to, from, next) {
     next(vm => {
       // vm 就是组件实例
       vm.loadData()
     })
   }
   

不过组合式 API 通常建议用 onMounted 替代这种“远古”写法。

  1. beforeRouteUpdate 是处理动态路由复用的关键。假如同一个组件展示不同 id 的文章,切换 id 时组件不会销毁重建(keep-alive 下更明显),此时 onMounted 不会重新触发,需要用这个钩子来响应参数变化。
  1. 守卫的完整执行顺序(导航触发后):
  • beforeRouteLeave(离开组件)
  • beforeEach(全局)
  • beforeRouteUpdate(复用组件,如果有)
  • beforeEnter(路由独享)
  • 解析异步路由组件
  • beforeRouteEnter(进入目标组件)
  • beforeResolve(全局)
  • 导航确认
  • afterEach(全局)
  • 触发组件生命周期

权限控制的标准实现

结合以上三种守卫,一个典型的中后台系统权限控制可以这样分步落地:

第一步:定义路由时标记权限

const routes = [
  { path: '/login', component: Login },
  {
    path: '/dashboard',
    component: Dashboard,
    meta: { requiresAuth: true, roles: ['admin', 'editor'] }
  },
  {
    path: '/settings',
    component: Settings,
    meta: { requiresAuth: true, roles: ['admin'] }
  }
]

第二步:全局守卫做统一登录拦截

router.beforeEach((to) => {
  const token = getToken()
  if (to.meta.requiresAuth && !token) {
    return `/login?redirect=${to.fullPath}`
  }
})

第三步:路由独享守卫(或全局守卫扩展)做角色校验

// 可以在全局守卫中加入角色判断,也可以像下面这样用路由独享守卫
{
  path: '/settings',
  component: Settings,
  meta: { roles: ['admin'] },
  beforeEnter(to, from, next) {
    const user = getUserInfo()
    if (!to.meta.roles.includes(user.role)) {
      next('/403')
    } else {
      next()
    }
  }
}

实际项目中,为了避免在每个路由里重复写角色判断逻辑,通常会把它封装成一个函数,并在全局守卫里统一读取 to.meta.roles 来判断,保持代码清爽。

总结:路由守卫让页面级的“安检”变得结构化。全局守卫管大门,路由独享守卫管敏感区域,组件内守卫管局部细节。三者搭配,既能写出安全的登录拦截,又不会让权限逻辑散落各处难以维护。