# 全局路由守卫
# 三种全局路由守卫
vue-router 提供的路由守卫主要用来通过跳转或取消的方式路由导航。全局的路由守卫指的是在每次路由跳转时都生效的路由守卫,一共有三种:
- beforeEach:全局前置路由守卫,在每次路由跳转前被触发
- beforeResolve:全局解析路由守卫,在路由被确认之前,同时在所有组件内守卫和异步路由组件被解析之后触发
- afterEach:全局后置路由守卫,在每次路由跳转后被触发
全局路由守卫接收三个参数:to、from、next(后置路由守卫不接受 next 参数)
- to:即将要进入的目标路由对象
- from:当前导航正要离开的路由
- next:执行下一个钩子,如果传入参数还可以进行路由的跳转。
这三个全局路由守卫的实现原理几乎相同,下面我们以 beforeEach 守卫为例讲解,以下所有的源码来自 mini-vue-router3:
# 实现原理
首先,在初始化 Vue Router 的时候,就会在 router 上为每个全局路由守卫开辟一个回调队列,用于存放该守卫传入的回调:
class VueRouter {
constructor(options) {
...
this.options = options
// 钩子对应的回调队列
this.beforeHooks = []
this.afterHooks = []
let routes = options.routes || []
...
}
当我们每次调用对应的钩子时,传入的回调就会被存储在相应的队列中,等待合适的时机执行:
/**
* 用于向钩子对应的回调队列中添加回调函数
* @param {Array} list 回调队列
* @param {Function} fn 要添加的回调函数
* @returns 返回一个删除刚加入的这个回调的函数
*/
function registerHook(list, fn) {
list.push(fn) // 加入回调队列
// 返回一个删除刚加入的这个回调的函数
return () => {
const i = list.indexOf(fn)
if (i > -1) list.splice(i, 1)
}
}
class VueRouter {
constructor(options) {
...
/**
* 全局前置路由守卫
* @param {Function} cb 回调函数
* @returns 删除此回调函数的方法
*/
beforeEach(cb) {
return registerHook(this.beforeHooks, cb)
}
/**
* 全局后置路由守卫
* @param {Function} cb 回调函数
* @returns 删除此回调函数的方法
*/
afterEach(cb) {
return registerHook(this.afterHooks, cb)
}
...
}
之前我们说到过,所有的路由跳转逻辑都必须走 transitionTo 方法,所以我们的前置路由守卫就在此方法内触发,在跳转前,先执行前置路由守卫对应回调队列中的所有回调:
transitionTo(location, callback) {
let record = this.router.match(location)
let route = createRoute(record, location)
let queue = this.router.beforeHooks
// 跳转的路径和匹配结果一致
if (location === this.current.path && route.matched.length === this.current.matched.length) {
// 拦截重复跳转
return
}
// 执行前置路由守卫
runQueue(this, queue, route, this.current, () => {
// 每次跳转,都要更新 current
this.current = route
if (callback) callback()
// 更新 _route 的值
this.cb && this.cb(route)
let afterQueue = this.router.afterHooks
// 执行后置路由守卫
runQueue(this, afterQueue, route, this.current, () => { })
})
全局路由守卫的实现原理较为简单,首先保存传人的回调,维护成一个队列,等待合适的时机依次将回调执行完毕即可。所有全局路由守卫的实现原理都是相同的,不过在 transitionTo 方法中触发回调的方式不同而已。