一、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前端网发表,如需转载,请注明页面地址。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。