一、vue router guard 到底是什么?
咱做Vue项目时,经常要控制页面能不能进、跳转前做些操作,这时候vue - router guard(路由导航守卫)就派上大用场啦!可不少刚接触的同学会疑惑:它到底是啥?不同类型咋用?权限管理咋结合?今天就用问答形式把这些事儿唠明白~
简单说,路由导航守卫就是路由跳转过程中的「钩子函数」,能在页面跳转的不同阶段(比如准备跳、跳的过程中、跳完之后),让咱插入自定义逻辑,举个生活里的例子:你进小区大门,门卫(守卫)会先查你有没有门禁卡(权限判断),允许进了再放行;或者进门前让你登记访客信息(数据处理),在Vue项目里,跳转页面、权限控制、加载数据这些常见需求,全靠它来实现~
路由跳转不是“嗖”一下直接到目标页,而是有个流程:导航被触发 → 在失活的组件里调用 leave 守卫 → 调用全局的 beforeEach 守卫 → 调用路由独享的 beforeEnter → 在激活的组件里调用 update 守卫(如果是组件复用情况)→ 调用 beforeRouteEnter(组件内守卫)→ 调用全局的 beforeResolve → 导航被确认 → 调用全局的 afterEach 守卫 → 触发 DOM 更新 → 调用 beforeRouteEnter 守卫里 next 的回调函数,创建好组件实例,守卫就是插在这些环节里的“操作入口”。
vue - router guard 有哪些类型?各自咋用?
vue - router 的守卫分三大类:全局守卫(作用于整个路由系统)、路由独享守卫(只作用于某一个路由)、组件内守卫(写在Vue组件里,针对当前组件的路由跳转),下面逐个拆解~
全局守卫:整个路由系统的“总闸”
全局守卫绑定在 router 实例上,不管跳哪个路由,都会触发,常见的有 router.beforeEach
、router.beforeResolve
、router.afterEach
这三个,作用和时机各有不同。
① router.beforeEach(全局前置守卫):跳转流程里最先触发的全局守卫,能直接控制“是否允许跳转”,它接收三个参数:to
(即将进入的目标路由对象)、from
(当前要离开的路由对象)、next
(放行/拦截的函数,必须调用才能继续导航)。
举个登录拦截的例子:后台系统里,部分页面需要登录才能进,咱可以用 beforeEach
全局判断登录状态:
// router/index.js const router = createRouter({ ... }) router.beforeEach((to, from, next) => { // 假设用localStorage存登录标识 const isLogin = localStorage.getItem('token') // 路由元信息meta.requiresAuth标记该页面是否需要登录 if (to.meta.requiresAuth) { if (isLogin) { next() // 已登录,放行 } else { next('/login') // 没登录,跳登录页 } } else { next() // 不需要登录的页面,直接放行 } })
这样所有需要登录的页面,都会被这个“总闸”拦下检查,权限控制一步到位~
② router.beforeResolve(全局解析守卫):触发时机在 beforeEach
之后,组件内守卫 beforeRouteEnter 之前,它和 beforeEach
最大的区别是:beforeResolve
会等“所有路由组件内守卫、异步路由组件加载完成”后才触发,适合做统一的解析操作(比如处理完所有异步数据后再跳转)。
举个场景:多个异步路由组件加载时,想等它们都加载完再处理逻辑,就可以用 beforeResolve
,不过日常开发里,它的出场率比 beforeEach
低些,大家重点记住触发时机和适用场景~
③ router.afterEach(全局后置守卫):路由跳转完成后触发,没有 next
函数(因为导航已经完成了),它主要用来做页面埋点、修改文档标题这类“跳转后操作”。
比如根据路由元信息改页面标题:
router.afterEach((to, from) => { // 假设路由配置里meta.title存了页面标题 document.title = to.meta.title || '默认标题' })
路由独享守卫:单个路由的“专属门卫”
路由独享守卫叫 beforeEnter
,写在路由规则里,只对当前路由生效,触发时机在全局 beforeEach 之后,组件内守卫之前,适合给单个路由加专属逻辑。
举个订单详情页加载数据的例子:进入订单详情页前,得先请求订单数据,否则页面会空着,用 beforeEnter
就能实现“数据加载完再放行”:
const routes = [ { path: '/order/:id', component: OrderDetail, // 路由独享守卫 beforeEnter: (to, from, next) => { const orderId = to.params.id // 从路由参数拿订单ID // 假设fetchOrderDetail是请求订单数据的函数 fetchOrderDetail(orderId).then(res => { // 把数据存到Vuex或组件里(这里用Vuex举例) store.commit('order/setDetail', res.data) next() // 数据存好后,放行进入页面 }).catch(err => { console.log('订单数据加载失败', err) next('/404') // 加载失败跳404页面 }) } } ]
这样进订单页时,数据已经备好,页面渲染就不会空着啦~
组件内守卫:当前组件的“贴身管家”
组件内守卫是写在 Vue 组件 里的钩子,针对当前组件的路由跳转,有三个:beforeRouteEnter
、beforeRouteUpdate
、beforeRouteLeave
,各自管不同阶段。
① beforeRouteEnter:组件创建前触发:此时组件实例(this
)还没生成,所以想访问组件实例,得在 next
的回调里操作,适合组件加载前初始化数据。
举个页面加载前请求数据的例子:
<template> <div>{{ userInfo.name }}</div> </template> <script> export default { data() { return { userInfo: {} } }, beforeRouteEnter(to, from, next) { // 此时this是undefined,因为组件还没创建 fetchUserInfo(to.params.userId).then(res => { // next的回调里,vm就是组件实例 next(vm => { vm.userInfo = res.data // 给组件数据赋值 }) }) } } </script>
② beforeRouteUpdate:组件复用时触发:当路由参数变化,但组件没销毁(比如从 /user/1
跳到 /user/2
,用户组件复用)时,这个守卫会触发,适合处理路由参数变化后的逻辑。
比如用户ID变了,重新加载用户信息:
export default { beforeRouteUpdate(to, from, next) { const newUserId = to.params.id this.fetchUser(newUserId) // 组件里的fetchUser方法,重新请求数据 next() // 放行 }, methods: { fetchUser(userId) { // 请求用户数据... } } }
③ beforeRouteLeave:离开组件时触发:跳转前,想拦截用户(比如表单没保存提醒),就靠它。
举个表单未保存提醒的例子:
export default { data() { return { formChanged: false, // 标记表单是否修改 formData: {} } }, beforeRouteLeave(to, from, next) { if (this.formChanged) { // 弹出确认框 const confirm = window.confirm('表单还没保存,确定离开吗?') if (confirm) { next() // 确定离开,放行 } else { next(false) // 取消离开,留在当前页 } } else { next() // 表单没修改,直接放行 } } }
怎么用路由守卫做权限管理?
后台管理系统里,不同角色(admin、editor)能访问的页面不一样,这时候路由守卫 + 路由元信息(meta)黄金搭档”,核心思路是:给路由加meta标记权限 → 全局守卫判断用户角色和meta是否匹配。
步骤1:给路由配置meta字段
在路由规则里,用 meta.role
标记该页面需要的角色:
const routes = [ // 管理员才能进的仪表盘 { path: '/admin/dashboard', component: AdminDashboard, meta: { role: 'admin' } }, // 编辑才能进的文章页 { path: '/editor/article', component: EditorArticle, meta: { role: 'editor' } }, // 登录页,不需要权限 { path: '/login', component: Login } ]
步骤2:全局守卫判断角色
用 router.beforeEach
全局拦截,判断用户角色是否符合路由要求,假设用户角色存在Vuex的 store.state.user.role
里:
router.beforeEach((to, from, next) => { const userRole = store.state.user.role // 如果路由需要权限(meta.role存在) if (to.meta.role) { if (userRole === to.meta.role) { next() // 角色匹配,放行 } else { next('/403') // 角色不匹配,跳权限不足页面 } } else { next() // 不需要权限的页面,直接放行 } })
这样一来,管理员进不了编辑页面,编辑也进不了管理员页面,权限控制就稳了~
步骤3:结合组件内守卫细化逻辑
全局守卫管页面级权限,组件内守卫可以管“页面内操作”,比如管理员进入仪表盘前,用 beforeRouteEnter
加载数据:
<template> <div>{{ dashboardData.title }}</div> </template> <script> export default { data() { return { dashboardData: {} } }, beforeRouteEnter(to, from, next) { fetchDashboardData().then(res => { next(vm => { vm.dashboardData = res.data }) }) } } </script>
要是有更细的权限(比如按钮级),可以在组件内根据角色控制渲染,但路由守卫主要负责“页面能不能进”这个大关卡~
用路由守卫容易踩哪些坑?咋避?
路由守卫功能强,但用不对容易出bug,下面列几个常见坑和解决办法~
next() 多次调用
坑:守卫里 next()
只能调用一次,调多次会报错,比如异步操作没处理好,导致 next()
调了两次:
// 错误示例!next()调了两次 router.beforeEach((to, from, next) => { if (to.meta.requiresAuth) { checkLogin().then(res => { next() // 第一次调 }) next() // 第二次调,触发报错! } })
解决:把 next()
放进异步回调里,或者用 if - else
控制,确保只调一次:
// 正确示例:next()只在异步回调里调 router.beforeEach((to, from, next) => { if (to.meta.requiresAuth) { checkLogin().then(res => { next() // 只调一次 }).catch(err => { next('/login') }) } else { next() } })
异步操作没等完就跳转
坑:在 beforeEnter
或 beforeRouteEnter
里请求数据时,没等请求完成就调用 next()
,导致组件拿到空数据。
// 错误示例:没等fetchData完成就next() beforeEnter: (to, from, next) => { fetchData() // 异步请求没等结果 next() // 直接放行,组件拿到空数据 }
解决:把 next()
放进异步操作的 then
里,确保数据加载完再放行:
// 正确示例:等数据加载完再next() beforeEnter: (to, from, next) => { fetchData().then(res => { store.commit('saveData', res) next() // 数据存好后再放行 }) }
守卫执行顺序搞混
坑:不同守卫触发时机不同,逻辑依赖顺序时容易出错,比如想在组件创建前加载数据,却用了全局 afterEach
(此时组件已经创建完了),导致数据加载时机不对。
解决:牢记守卫执行顺序(前面讲过的流程):
全局 beforeEach
→ 路由独享 beforeEnter
→ 组件内 beforeRouteEnter
→ 全局 beforeResolve
→ 组件实例创建 → 全局 afterEach
。
逻辑要放在对应时机的守卫里,组件创建前加载数据”→ 用 beforeRouteEnter
;“全局统一权限判断”→ 用 beforeEach
。
组件内 beforeRouteEnter 拿不到 this
坑:beforeRouteEnter
触发时,组件还没实例化,直接用 this
会报错 undefined
。
解决:要访问组件实例,必须在 next
的回调里操作,vm
就是组件实例:
beforeRouteEnter(to, from, next) { next(vm => { vm.doSomething() // 正确,vm是组件实例 }) }
要是直接写 this.doSomething()
,肯定报错~
把这些坑避开,路由守卫用起来就顺多啦~
vue - router guard 是路由跳转的“控制中心”,不同类型的守卫各司其职,从全局到单个路由再到组件内,覆盖了跳转的每个环节,只要把类型、用法、权限结合、避坑技巧吃透,就能轻松用它管控页面跳转、做权限管理,让项目路由逻辑更丝滑~要是你还有其他疑问,评论区随时喊我~
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。