Vue Router 有哪些导航守卫?完整的导航流程是怎样的?
导航守卫是路由跳转过程中的钩子,分为全局守卫(beforeEach、beforeResolve、afterEach)、路由独享的 beforeEnter 和组件内守卫。Vue Router 4 推荐用返回值控制跳转:返回 false 取消,返回路由地址重定向,不返回或返回 true 放行。
简答
用户从列表页点进详情页时,路由不是立刻切过去的,中间会经过一串钩子,每个钩子都能决定放行、取消还是改去别的页面,这些钩子就叫导航守卫。最常见的用法是在
router.beforeEach里检查登录状态,没登录就跳到登录页。守卫按作用范围分三类。全局守卫注册在 router 上,有
beforeEach、beforeResolve和afterEach;路由独享守卫是写在路由配置里的beforeEnter;组件内守卫写在组件里,有beforeRouteEnter、beforeRouteUpdate和beforeRouteLeave。afterEach在导航确认后才执行,不能再改变导航,适合埋点和改页面标题。完整顺序是:先调用要离开的组件的
beforeRouteLeave,再调用全局beforeEach,然后是复用组件的beforeRouteUpdate、路由配置的beforeEnter,接着解析异步路由组件,调用要进入的组件的beforeRouteEnter、全局beforeResolve,导航确认后调用afterEach,最后更新 DOM。要注意写法上的版本差别。Vue Router 3 的守卫必须调用第三个参数
next()才会继续;Vue Router 4 推荐直接返回值,返回false取消,返回一个路由地址就重定向,不返回或返回true就放行。next在 4 里仍然支持,但每次经过守卫必须恰好调用一次,漏调或多调都会出问题。
用 beforeEach 做登录检查
下面是 Vue Router 4 的写法,需要登录的路由在 meta 里标记 requiresAuth:
router.beforeEach(async (to, from) => {
if (to.meta.requiresAuth && !isAuthenticated() && to.name !== 'Login') {
return { name: 'Login', query: { redirect: to.fullPath } };
}
});
没有登录时,守卫返回登录页的地址,当前导航就被丢弃,换成一次去登录页的新导航。query.redirect 记下了原本想去的页面,登录成功后可以跳回来。
为什么要判断 to.name !== 'Login'?因为去登录页本身也是一次导航,也会经过 beforeEach。不加这个判断,跳到登录页时又会被重定向到登录页,形成无限重定向。
守卫可以是 async 函数,比如先 await 拉取用户信息再判断。Vue Router 会等它完成,导航在此期间处于等待状态。
Vue Router 3 的 next 写法为什么容易出错
Vue Router 3 中,守卫必须调用 next 才能继续:
// Vue Router 3 中有问题的写法
router.beforeEach((to, from, next) => {
if (to.name !== 'Login' && !isAuthenticated()) next({ name: 'Login' });
next();
});
没登录时,这段代码先调用了 next({ name: 'Login' }),接着又执行到了 next(),同一次守卫里调用了两次。正确写法是用 else 把两条路径分开,保证每次只调一次。反过来,如果某个分支忘了调用 next,导航会一直卡住不动。
正是因为这类错误太常见,Vue Router 4 改成了用返回值表达结果,next 只是为了兼容保留下来。
组件内守卫各自什么时候用
beforeRouteLeave 在离开当前页面前调用,常用来拦截未保存的表单:
beforeRouteLeave() {
if (this.hasUnsavedChanges && !window.confirm('还有未保存的修改,确定离开吗?')) {
return false;
}
}
beforeRouteUpdate 用在路由变了、组件却被复用的情况。比如从 /user/1 跳到 /user/2,两个地址用的是同一个 User 组件,组件不会重新创建,created、mounted 也不会再执行。这时要在 beforeRouteUpdate 里根据新的 id 重新拉数据,或者用 watch 侦听路由参数。
beforeRouteEnter 比较特殊,它在导航确认之前调用,这时组件实例还没有创建,所以不能用 this。如果确实要访问实例,可以给 next 传一个回调,等实例创建好以后执行:next((vm) => { ... })。这是唯一支持给 next 传回调的守卫。
组合式 API 里没有 beforeRouteEnter 的对应写法,另外两个可以用 onBeforeRouteLeave 和 onBeforeRouteUpdate。
常见追问
权限控制只靠路由守卫够吗?
不够。路由守卫只是在前端拦住页面跳转,用户可以直接调接口,也可以改前端代码绕过去。真正的权限校验必须放在服务端,前端守卫只负责体验。完整的分层设计见《前端权限控制怎样设计路由、按钮和接口三层?》。
beforeEach 和 beforeResolve 有什么区别?
beforeResolve在所有组件内守卫和异步路由组件都解析完之后、导航确认之前调用,比beforeEach晚。需要在确定要进入页面后再做的检查,比如申请摄像头权限,适合放在beforeResolve,这样不会因为后面的守卫取消了导航而白做。
动态添加的路由,守卫还会生效吗?
会。全局守卫对所有导航都生效,包括用
router.addRoute动态添加的路由。常见做法是登录后拉取权限,用addRoute加上有权限的路由,再重新导航一次到目标地址,让新路由生效。