Vue-Router源码解析:导航守卫

回顾

如果有小伙伴想要回顾之前的知识的话,点击下面的链接查看之前所有的源码分析流程👇
《Vue-Router源码解析:目录》

queue

上一章节我们分析了transitionTo,在transitionTo中有这样的一段定义:

    const queue: Array<?NavigationGuard> = [].concat(
      // in-component leave guards
      //在失活组件中调用 beforeRouteLeave
      extractLeaveGuards(deactivated),
      // global before hooks
      //调用 beforeEach
      this.router.beforeHooks,
      // in-component update hooks
      //调用 beforeRouteUpdate
      extractUpdateHooks(updated),
      // in-config enter guards
      //调用激活组件中的 beforeEnter
      activated.map((m) => m.beforeEnter),
      // async components
      resolveAsyncComponents(activated)
    )

这边使用了一个空数组去concat一些其他的数组。这一篇章我们分析的重点就是这个queue,同时也是VueRouter导航守卫的各种触发时机。
我们先看看extractLeaveGuards(deactivated)

beforeRouteLeave

extractLeaveGuards

function extractLeaveGuards(deactivated: Array<RouteRecord>): Array<?Function> {
  return extractGuards(deactivated, 'beforeRouteLeave', bindGuard, true)
}

调用了extractGuards,传入了deactivateddeactivated是使用resolveQueue方法后返回的,是一个失活的路由数组。
进入extractGuards

extractGuards

function extractGuards(
  records: Array<RouteRecord>,
  name: string,
  bind: Function,
  reverse?: boolean
): Array<?Function> {
  const guards = flatMapComponents(records, (def, instance, match, key) => {
    const guard = extractGuard(def, name)
    if (guard) {
      return Array.isArray(guard)
        ? guard.map((guard) => bind(guard, instance, match, key))
        : bind(guard, instance, match, key)
    }
  })
  //beforeRouteLeave 要翻转成先子后父
  return flatten(reverse ? guards.reverse() : guards)
}

一开始先调用了flatMapComponents,传入了deactivated和一个方法。我们进入flatMapComponents,进入。

flatMapComponents

export function flatMapComponents(
  matched: Array<RouteRecord>,
  fn: Function
): Array<?Function> {
  return flatten(
    matched.map((m) => {
      return Object.keys(m.components).map((key) =>
        fn(m.components[key], m.instances[key], m, key)
      )
    })
  )
}

先不看flatten具体实现,我们先看看flatten的参数,对matched也就是deactivated使用map返回一个数组,这个数组也是由map生成的,具体生成的逻辑是遍历deactivated中每个route record中的components属性上的所有key,然后调用回调,传入components[key],也就是对应的组件。我们再去看看回调的定义:

(def, instance, match, key) => {
    const guard = extractGuard(def, name)
    if (guard) {
      return Array.isArray(guard)
        ? guard.map((guard) => bind(guard, instance, match, key))
        : bind(guard, instance, match, key)
    }
  }

首先调用extractGuard,传入def(就是组件对象)和namebeforeRouteLeave),进入extractGuard

extractGuard

function extractGuard(
  def: Object | Function,
  key: string
): NavigationGuard | Array<NavigationGuard> {
  if (typeof def !== 'function') {
    // extend now so that global mixins are applied.
    def = _Vue.extend(def)
  }
  return def.options[key]
}

如果def(也就是组件对象或者方法)不是一个函数的话,就会调用Vue.extend创建一个组件构造器,之后返回组件上的beforeRouteLeave方法。

也就是说flatMapComponents回调中的guard就是用户定义的beforeRouteLeave方法。
之后就是调用bind返回beforeRouteLeave的方法的调用函数了(此时并没有真正调用beforeRouteLeave,只是生成一个可以用来触发beforeRouteLeave的方法而已,具体可以看bind的定义)。

我们再回到flatMapComponents中:

export function flatMapComponents(
  matched: Array<RouteRecord>,
  fn: Function
): Array<?Function> {
  return flatten(
    matched.map((m) => {
      return Object.keys(m.components).map((key) =>
        fn(m.components[key], m.instances[key], m, key)
      )
    })
  )
}

flatten方法其实很简单,就是使用了一个空数组去concat后面生成的hooks函数数组。
最后这个方法返回的就是一个包含所有激活路由的beforeRouteLeave钩子函数的数组。所以在queue中的第一部分就是beforeRouteLeave钩子。

我们再来看看queue中的第二部分。

beforeEach

this.router.beforeHooks

    const queue: Array<?NavigationGuard> = [].concat(
      // in-component leave guards
      //在失活组件中调用 beforeRouteLeave
      extractLeaveGuards(deactivated),
      // global before hooks
      //调用 beforeEach
      this.router.beforeHooks,
      // in-component update hooks
      //调用 beforeRouteUpdate
      extractUpdateHooks(updated),
      // in-config enter guards
      //调用激活组件中的 beforeEnter
      activated.map((m) => m.beforeEnter),
      // async components
      resolveAsyncComponents(activated)
    )

第二部分就是router实例上的beforeHooks实例属性。我们进入class VueRouter看看this.router.beforeHooks是如何被赋值的:

class VueRouter{
  ...
  beforeEach(fn: Function): Function {
    return registerHook(this.beforeHooks, fn)
  }
  ...
}

function registerHook(list: Array<any>, fn: Function): Function {
  list.push(fn)
  return () => {
    const i = list.indexOf(fn)
    if (i > -1) list.splice(i, 1)
  }
}

由这两段逻辑可以发现,当用户使用router.beforeEach((to,from,next)=>{})定义钩子函数的时候,会被VueRouter把所有的beforeEach都挂载到VueRouter实例上的this.beforeHooks实例属性上。所以在queue中的第二部分就是用户定义的beforeEach钩子。

beforeRouteUpdate

我们再看看queue中的第三部分:

    const queue: Array<?NavigationGuard> = [].concat(
      // in-component leave guards
      //在失活组件中调用 beforeRouteLeave
      extractLeaveGuards(deactivated),
      // global before hooks
      //调用 beforeEach
      this.router.beforeHooks,
      // in-component update hooks
      //调用 beforeRouteUpdate
      extractUpdateHooks(updated),
      // in-config enter guards
      //调用激活组件中的 beforeEnter
      activated.map((m) => m.beforeEnter),
      // async components
      resolveAsyncComponents(activated)
    )

extractUpdateHooks,传入了updated,这边的逻辑和上面的extractLeaveGuards逻辑一模一样,只不过name换成了beforeRouteUpdate,这边就不多说了,有兴趣的同学自己再去过一遍好了。

beforeEnter

第四部分,很简单,就是调用所有activated组件中定义的beforeEnter钩子。

异步组件挂载

第五部分,调用了resolveAsyncComponents,是异步组件的挂载机制,这边我就不详细讲了,因为这不是这边的主要逻辑,有兴趣的同学可以去看看Vue源码中是如何挂载异步组件的。

beforeRouteEnter

这时我们可以看看VueRouter官方的导航守卫流程:
导航解析流程
好像还差beforeRouterEnterbeforeResolveafterEach。这三个导航守卫没有触发。我们先来看看beforeRouterEnter
在上一章节:《Vue-Router源码解析:transitionTo》我们提到了runQueue中有一个回调函数,当时没有细说,这次我们来看看runQueue的回调函数定义:

    runQueue(queue, iterator, () => {
      // wait until async components are resolved before
      // extracting in-component enter guards
      const enterGuards = extractEnterGuards(activated)
      //调用 beforeResolve 钩子
      const queue = enterGuards.concat(this.router.resolveHooks)
      runQueue(queue, iterator, () => {
        if (this.pending !== route) {
          return abort(createNavigationCancelledError(current, route))
        }
        this.pending = null
        onComplete(route)
        if (this.router.app) {
          this.router.app.$nextTick(() => {
            handleRouteEntered(route)
          })
        }
      })
    })

首先调用extractEnterGuards并传入activated(激活的路由),前面我们已经使用过extractLeaveGuards,总体来说两个方法是差不多的,只是传入的name不一样,这里传入的namebeforeRouteEnter

我们回到runQueue的回调:

    runQueue(queue, iterator, () => {
      // wait until async components are resolved before
      // extracting in-component enter guards
      const enterGuards = extractEnterGuards(activated)
      //调用 beforeResolve 钩子
      const queue = enterGuards.concat(this.router.resolveHooks)
      runQueue(queue, iterator, () => {
        if (this.pending !== route) {
          return abort(createNavigationCancelledError(current, route))
        }
        this.pending = null
        onComplete(route)
        if (this.router.app) {
          this.router.app.$nextTick(() => {
            handleRouteEntered(route)
          })
        }
      })
    })
  }

接着刚刚的代码,接下来会执行const queue = enterGuards.concat(this.router.resolveHooks),之后又会把enterGuardsqueue拼接起来继续执行一次runQueue,不过这次的回调函数不太一样。
所以,这个时候就会触发用户定义在组件内的beforeRouteEnter钩子(这个钩子访问不到Vue实例,具体可以看看extractEnterGuardsinstance这个参数传入的是_)和this.router.resolveHooks

beforeResolve

那么这个this.router.resolveHooks又是什么呢?
我们可以在class VueRouter中找到this.router.resolveHooks的定义:

  beforeResolve(fn: Function): Function {
    return registerHook(this.resolveHooks, fn)
  }

所以这个resolveHooks就是用户定义的beforeResolve钩子。

afterEach

我们再来看看第二个runQueue的回调函数:

 () => {
        if (this.pending !== route) {
          return abort(createNavigationCancelledError(current, route))
        }
        this.pending = null
        onComplete(route)
        if (this.router.app) {
          this.router.app.$nextTick(() => {
            handleRouteEntered(route)
          })
        }
      }

在这里面会调用一个onComplete(route),方法,这个onComplete是传进来的参数,往上面一直找的话可以找到onComplete的定义:

      () => {
        //导航被确认
        this.updateRoute(route)
        //调用 onComplete 回调
        onComplete && onComplete(route)
        this.ensureURL()
        //调用 afterEach 钩子
        this.router.afterHooks.forEach((hook) => {
          hook && hook(route, prev)
        })

        // fire ready cbs once
        if (!this.ready) {
          this.ready = true
          this.readyCbs.forEach((cb) => {
            cb(route)
          })
        }
      },

在这先会确认导航,然后调用transitionTo传入的完成回调,之后会遍历afterHooks然后挨个调用,这个afterHooks我们可以看class VueRouter

  afterEach(fn: Function): Function {
    return registerHook(this.afterHooks, fn)
  }

所以这个afterHooks就是用户定义的afterEach钩子。

总结

所以根据源码可以看出Vue导航守卫触发的顺序为:beforeRouteLeave => beforeEach => beforeRouteUpdate => beforeEnter => beforeRouteEnter => beforeResolve => afterEach

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱学习的前端小黄

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值