# 全局路由守卫

# 三种全局路由守卫

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 方法中触发回调的方式不同而已。