一、vue-router路由守卫到底是干啥的?
咱做Vue项目的时候,路由跳转这块经常要搞权限控制、数据加载、离开提示这些事儿,vue-router的「路由守卫」就是解决这些问题的好帮手!但刚接触的同学往往一头雾水:路由守卫到底是干啥的?分哪几类?咋用才不会踩坑?今天咱用白话+实际例子,把路由守卫的核心知识和常见问题一次性讲透~
你可以把路由守卫理解成「路由的门卫」—— 当你从页面A跳转到页面B时,整个跳转过程会经过好几个“检查点”,每个检查点都能插一段逻辑:比如判断用户有没有登录、提前加载页面数据、离开时提醒保存……举个生活里的例子:你进小区(路由跳转),小区大门(全局守卫)先查你有没有门禁卡(登录态);要是去快递柜(某个路由),快递柜旁的保安(路由独享守卫)再查你有没有取件码(特殊权限);进家门(组件内)前,还得确认门关好没(组件内守卫,离开时检查)。
本质上,路由守卫是在「路由生命周期」的关键节点植入逻辑,让路由跳转的每一步都能被控制,解决权限、数据、交互这些实际问题。
路由守卫分哪些类型?各自啥时候触发?
路由守卫分三大类:全局守卫、路由独享守卫、组件内守卫,不同类型的触发时机、作用范围完全不一样,咱一个个拆:
全局守卫:管整个应用的所有路由
全局守卫作用于所有路由跳转,不管你从哪跳到哪,都会触发,常见的有3个钩子:
- beforeEach:路由跳转前触发(最常用!),用户没登录就想进个人中心?拦下来跳登录页”。
- beforeResolve:在
beforeEach、路由独享守卫、组件内守卫 之后,afterEach之前触发,相当于“最后一次全局拦截机会”,适合做全局的最终校验。 - afterEach:路由跳转完成后触发,适合做“收尾活”,比如关闭加载动画、埋点统计。
举个全局beforeEach的例子(登录拦截):
// 在main.js里配置全局守卫
router.beforeEach((to, from, next) => {
const isLogin = localStorage.getItem('token') // 从本地取登录态
if (to.path === '/login') {
next() // 要去登录页,直接放行
} else {
isLogin ? next() : next('/login') // 没登录就跳登录页
}
})
这里to是“要去的路由”,from是“从哪来的路由”,next是“放行/跳转到哪”的函数。
路由独享守卫:只管某一个路由
路由独享守卫叫beforeEnter,只作用于某一个具体路由,后台管理页只有管理员能进”,就在这个路由的配置里写守卫。
举个路由独享的例子(管理员页面权限):
// 路由配置文件(比如router/index.js)
{
path: '/admin',
component: Admin,
beforeEnter: (to, from, next) => {
const role = localStorage.getItem('role')
role === 'admin' ? next() : next('/403') // 不是管理员跳403页面
}
}
只有访问/admin时,这个守卫才会触发,其他路由完全不管~
组件内守卫:管当前组件的路由变化
组件内守卫是写在组件内部的钩子,针对“当前组件对应的路由”的变化,常见的有3个:
- beforeRouteEnter:进入该组件对应的路由前触发(此时组件还没创建,拿不到
this)。 - beforeRouteUpdate:当前组件对应的路由参数变化时触发(
/user/1 → /user/2,组件复用没销毁)。 - beforeRouteLeave:离开该组件对应的路由前触发(比如从详情页跳列表页,离开前提示)。
举个beforeRouteEnter的例子(进页面前加载数据):
// Detail.vue(商品详情组件)
export default {
name: 'Detail',
data() { return { goodsData: {} } },
beforeRouteEnter(to, from, next) {
// 发请求拿商品数据(to.params.id是路由参数)
getGoodsDetail(to.params.id).then(res => {
// 组件还没创建,this拿不到,所以用next的回调传数据
next(vm => {
vm.goodsData = res.data // vm是组件实例,给data赋值
})
})
}
}
路由守卫里的next函数,为啥总踩坑?
next是路由守卫的核心函数,控制“放不放行、往哪放行”,但新手很容易在这翻车,咱总结3个高频坑:
坑1:多次调用next
next只能调用一次!如果在逻辑里写了多个next,会触发“NavigationDuplicated”错误。
反例(错误写法):
router.beforeEach((to, from, next) => {
if (to.path === '/login') {
next() // 第一次调用next
}
next() // 又调用一次,报错!
})
正确写法:用if-else确保next只执行一次:
router.beforeEach((to, from, next) => {
if (to.path === '/login') {
next()
} else {
next('/login')
}
})
坑2:异步操作没等完就next
如果守卫里有异步操作(比如发请求判断权限),没等异步完成就调用next,会导致判断逻辑“失效”。
反例(错误写法,没等请求完就next):
router.beforeEach((to, from, next) => {
checkLogin().then(isLogin => { // checkLogin是异步函数
if (!isLogin) next('/login')
})
next() // 这里会直接放行,不管checkLogin的结果
})
正确写法(用async/await等异步完成):
router.beforeEach(async (to, from, next) => {
const isLogin = await checkLogin() // 等异步结果
if (to.meta.requiresAuth && !isLogin) {
next('/login')
} else {
next()
}
})
坑3:在beforeRouteEnter里直接用this
beforeRouteEnter触发时,组件还没创建,所以this是undefined,必须通过next(vm => {})才能拿到组件实例。
反例(错误写法,直接用this):
beforeRouteEnter(to, from, next) {
this.goodsData = {} // this是undefined,报错!
next()
}
正确写法(用next的回调):
beforeRouteEnter(to, from, next) {
getGoodsDetail(to.params.id).then(res => {
next(vm => {
vm.goodsData = res.data // vm是组件实例
})
})
}
路由守卫在实际项目里能解决哪些问题?
路由守卫不是花里胡哨的功能,而是实实在在解决开发痛点的,咱列几个高频场景:
权限控制(最核心场景)
- 全局拦截:用
beforeEach拦所有页面,判断登录态、角色,没登录就跳登录页,普通用户不让进管理员页”。 - 单个路由拦截:用
beforeEnter拦特殊页面(比如支付页必须先下单,否则跳订单页)。
数据预加载
进页面之前把数据请求好,避免页面“闪白”。
- 详情页用
beforeRouteEnter发请求,把数据传给组件。 - 列表页用
beforeEach提前请求列表数据,存到vuex里。
页面过渡与埋点
- 加载动画:全局
afterEach里关闭加载动画,比如路由跳转时显示loading,跳完关掉:router.afterEach(() => { store.commit('hideLoading') // 调用vuex的mutation关loading }) - 埋点统计:
afterEach里记录用户访问路径,传给后端统计。
离开页面确认
表单页用beforeRouteLeave,判断用户是否修改了表单,没保存就弹窗提示:
beforeRouteLeave(to, from, next) {
if (this.formEdited) { // formEdited是判断表单是否修改的标记
const confirm = window.confirm('表单没保存,确定离开?')
confirm ? next() : next(false) // 确定就放行,否则留在当前页
} else {
next()
}
}
路由参数变化时更新数据
比如用户页/user/:id,id从1变2时,组件会复用(不会销毁重建),这时用beforeRouteUpdate重新请求数据:
beforeRouteUpdate(to, from, next) {
this.getUserData(to.params.id) // 调用方法,拿新id的数据
next()
}
和其他路由拦截方式比,路由守卫优势在哪?
有人会问:“我自己在组件里写watch $route也能拦截路由变化,为啥用路由守卫?” 咱对比下优势:
- 粒度更细:全局、单个路由、组件内,不同层级想拦就拦,比如全局守卫管所有页面,路由独享管单个页面,组件内管当前组件,灵活度拉满。
- 流程更顺:路由跳转的“生命周期”里每个阶段(跳转前、跳转中、跳转后)都有对应钩子,能精准控制,比如
beforeEach拦在最前面,afterEach收尾,逻辑不打架。 - 结合Vue生态更丝滑:和组件生命周期、vuex配合自然,比如在
beforeRouteEnter里调vuex的action拿数据,再传给组件。 - 错误处理更方便:通过
next(false)或跳错误页(如403、404),统一处理“权限不足”“页面不存在”等情况,不用在每个组件里写重复逻辑。
学路由守卫前,得先掌握哪些基础?
想玩转路由守卫,得先把这些基础打牢:
- vue-router基本使用:会配路由(动态路由、嵌套路由),会用
<router-link>和router.push跳转。 - 组件生命周期:清楚
created、mounted这些钩子的执行顺序,因为组件内守卫和生命周期有先后(比如beforeRouteEnter在created之前触发)。 - JS异步操作:懂
async/await、Promise,因为守卫里经常要处理异步请求(比如判断登录态、拉取数据)。 - 路由元信息(meta):给路由加
meta字段(比如meta: { requiresAuth: true }),在守卫里通过to.meta.requiresAuth判断是否需要权限。
新手常见疑问集中解答
最后解决几个新手高频疑问,帮你少走弯路:
全局守卫和组件内守卫的执行顺序是啥?
比如从A组件跳B组件,执行顺序是:
全局beforeEach → (如果B路由有beforeEnter)路由独享beforeEnter → 全局beforeResolve → B组件的beforeRouteEnter → 全局afterEach。
简单说:全局守卫是最外层,然后是路由独享,最后是组件内守卫。
beforeRouteEnter里拿不到this,除了next(vm => {})还有别的办法吗?
可以把数据存vuex或全局变量,但next(vm => {})是最直接的方式——因为它会等组件创建好后,再把数据传给组件实例,逻辑最顺。
afterEach里能阻止路由跳转吗?
不能!afterEach是路由跳转完成后触发的,所以没有next函数,只能做“收尾操作”(比如关loading、埋点),没法拦截跳转。
路由守卫里能改路由参数吗?
可以!在next里传新的路由配置,
next({
path: '/newPath',
query: { tab: 'goods' }, // 传查询参数
params: { id: 123 } // 传动态参数
})
这相当于“中断当前跳转,重新发起一个路由跳转”。
折腾一圈下来,你会发现路由守卫就是vue-router给咱的「路由控制权」—— 从全局到单个路由再到组件内,想在哪拦就在哪拦,想干啥逻辑就干啥逻辑~ 关键是把每个守卫的触发时机、next的用法吃透,再结合项目里的权限、数据、交互需求,就能玩得很溜,要是刚开始练,建议先从全局beforeEach做登录拦截入手,再慢慢拓展到组件内守卫处理表单保存这些细节,多写几个例子自然就通啦~
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
code前端网



发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。