资讯专栏INFORMATION COLUMN

一篇搞定vue-Router导航守卫

不知名网友 / 3183人阅读

摘要:如果我们不掉用守卫中的,迭代器的肯定并不会执行,守卫的迭代就停止了,守卫堵塞并不会执行完毕,也就不会由后面的更细路由操作了。

vue-router导航守卫

在本期文章中,我将为大家梳理弄明白以下几个事情,

1:导航守卫的执行顺序是怎么样的? 2:导航守卫中的next的用处? 3:为什么afterEach守卫没有next? 4:beforeEach是否可以叠加? 5:路由跳转经历了哪几部分?

在之前说过的一个内容router实例的history属性帮助我们做了所有跳转部分的事情,所以导航守卫的内容也在history中。

我们以HTML5History这个类来看一下这个push方法,

push (location: RawLocation, onComplete?: Function, onAbort?: Function) {
    const { current: fromRoute } = this
    this.transitionTo(location, route => {
      pushState(cleanPath(this.base + route.fullPath))
      handleScroll(this.router, route, fromRoute, false)
      onComplete && onComplete(route)
    }, onAbort)
  }

push(我们跳转时的$router.push就是这个方法)过程中调用了transitionTo完成了一系列的跳转内容,但这个方法在HTML5的类中并不存在,继承于base.js类中的方法
transitionTo就是实现路由跳转的方法
transitionTo的主流程是由confirmTranstion方法于uodateRoute方法结合起来的,翻译成普通话:路由跳转要先经过一个确认跳转的过程,在确认过程完成后进行一次路由的更新操作,

transitionTo (location: RawLocation, onComplete?: Function, onAbort?: Function) {
    // 获取要跳转的并且经过处理的路由
    const route = this.router.match(location, this.current)
    // confirmTranstion确认跳转过程
    this.confirmTransition(route, () => {
      // 确认完毕后完成更新路由操作
      this.updateRoute(route)
      onComplete && onComplete(route)
      this.ensureURL()

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

confirmTransiton做了什么呢?首先判断一下你是不是相同的路由。如果是那就什么都不做,第二步呢,我们要开始收集一波守卫了,然后把守卫收集起来,然后把每个守卫执行一遍,confirmTransition就算执行成功了。

下面是部分源码截图:

这个过程中的难点是什么?

如何收集守卫组合成守卫队列? 如何按顺序执行守卫的同时可以随时中止守卫队列? 如何找到守卫队列执行完毕后的那个节点(守卫队列执行完可以通知一下)

在vue-router中封装了一个runQueue函数来解决上面的三个问题的后两个。第一个问题呢则涉及到vue-router处理路由的一个大篇章,我们着重讲一下runQueue函数

runQueue函数的思想:

1:迭代器模式来保证遍历队列时每一步都是可控的,

2:队列完成后执行对应的回调函数,

推断出函数参数的对应功能:

queue : 需要执行的守卫队列

fn : 迭代器函数,守卫队列的每一个守卫都去执行迭代器函数

fn的第二个参数使迭代器进入下一步,不掉用就不会进入下一步(很重点)

cb : 结束时调用的回调函数

export function runQueue (queue: Array, fn: Function, cb: Function) {
  const step = index => {
  // 队列里已经没有内容可以执行了,那就代表队列执行完成了
    if (index >= queue.length) {
      cb()
    } else {
      // 如果队列内容存在就执行迭代函数
      if (queue[index]) {
        fn(queue[index], () => {
          step(index + 1)
        })
      // 什么也没有那就到下一步了        
      } else {
        step(index + 1)
      }
    }
  }
  // 启动了
  step(0)
}

runQueue是怎么帮助我们解决守卫队列处理的问题就算说完了。

(快留起来,这函数简直吊极了!)

处理守卫队列的大锤子我们已经制造好了,可以开工了,那你的守卫队列呢??

对对对,还有守卫队列要收集。
这个时候我们要想想有哪些守卫?

守卫有两大种类:前置守卫、后置守卫。

前置守卫:

全局的前置守卫: beforeEach beforeResolve

路由独享的守卫: beforeEnter

组件内的守卫: beforeRouterEnter、beforeRouterUpdate、beforeRouteLeave

后置守卫:

全局的后置守卫: afterEach

我们要想一下这些守卫都是怎么注册的,

在路由实例注册的:

beforeEach、beforeResolve、afterEach

在路由配置中注册的(路由独享守卫):

beforeEnter

组件内的路由守卫:

beforeRouteLeave、beforeRouteUpdate、beforeRouteEnter

好了我们要去榨取对应的守卫了,

confirmTransition的守卫分为两个队列:我们先来看第一个队列

 // 拿到路由跳转中更新、摧毁、激活时对应展示的组件。
 const {
      updated,
      deactivated,
      activated
    } = resolveQueue(this.current.matched, route.matched)
    // 路由守卫
    const queue: Array = [].concat(
      // in-component leave guards
      extractLeaveGuards(deactivated),
      // global before hooks
      this.router.beforeHooks,
      // in-component update hooks
      extractUpdateHooks(updated),
      // in-config enter guards
      activated.map(m => m.beforeEnter),
      // async components
      resolveAsyncComponents(activated)
    )

一个queue的顺序:

拿到被摧毁的组件的,榨取出所有组件内的离开守卫。

全局的beforeEach组件。

拿到更新的所有组件,榨取出所有组件内的更新守卫。

遍历要进入的路由,拿到所有路由的独享守卫。

加载要被激活的异步组件

7个守卫中的4个守卫都在被按顺序拿出来了,放入第一个queue。

再下一步要有一个处理守卫的迭代器:

我们该如何处理守卫?

保证在守卫中可以停止并且跳转到其余路由,

保证守卫可以正常通过,

const iterator = (hook: NavigationGuard, next) => {
      if (this.pending !== route) {
        return abort()
      }
      try {
        hook(route, current, (to: any) => {
          // 传个false就直接执行路由的错误处理,然后停止什么都不做。
          if (to === false || isError(to)) {
            // next(false) -> abort navigation, ensure current URL
            this.ensureURL(true)
            abort(to)
          } else if (
          // 如果我们接受了一个可以操作的路径。
            typeof to === "string" ||
            (typeof to === "object" && (
              typeof to.path === "string" ||
              typeof to.name === "string"
            ))
          ) {
            // next("/") or next({ path: "/" }) -> redirect
            abort()
            // 我们就执行路由跳转操作,并且守卫队列停止下面的迭代
            if (typeof to === "object" && to.replace) {
              this.replace(to)
            } else {
              this.push(to)
            }
          } else {
            // confirm transition and pass on the value
            // 接续迭代下去咯
            next(to)
          }
        })
      } catch (e) {
        abort(e)
      }
    }

next函数,之前在将runQueue的函数的时候,fn接收第二个参数(之前画过重点),第二个参数的回调函数是完成迭代器向下一步执行的功能。

下面会有一点乱:

所有的前置守卫都接收三个参数

beforeEnter(to,from,next)=>{
    //这个next就是我们看到的 hook里面接收的箭头函数((to:any)=>{})
    //这个箭头函数里面对迭代器的next进行了一下掉用,
    //保证在一定情况下迭代器可以向下走一步。
    next("/index")
    // 我们在这种next("/index")传递一个可以执行的路径时,(to:any)=>{}
    //这个箭头函数并不会调用迭代的next,而是跳转别的路径执行了push操作。
    // 如果我们不掉用守卫中的next,迭代器的next肯定并不会执行,守卫的迭代就停止了,
    // 守卫堵塞confirmTransition并不会执行完毕,也就不会由后面的更细路由操作了。
}
runQueue(queue, iterator, () => {
      const postEnterCbs = []
      const isValid = () => this.current === route
      // wait until async components are resolved before
      // extracting in-component enter guards
      const enterGuards = extractEnterGuards(activated, postEnterCbs, isValid)
      const queue = enterGuards.concat(this.router.resolveHooks)
      runQueue(queue, iterator, () => {
        if (this.pending !== route) {
          return abort()
        }
        this.pending = null
        onComplete(route)
        if (this.router.app) {
          this.router.app.$nextTick(() => {
            postEnterCbs.forEach(cb => { cb() })
          })
        }
      })
    })

我们在把第一个queue(四个守卫与一个异步组件的加载)执行完毕后,要收集与执行第二个queue了,

第二个queue:

收集了被的激活组件内的进入守卫

全局的beforeResolve的守卫

收集完开始执行第二个queue的迭代。第二个queue执行完执行一下onComplete函数,代表着confirmTransition方法执行完毕了。确认路由的过程结束了,

下面就是updateRoute的过程。updateRoute的时候执行全部的后置守卫,因为更新路由之后,当前的路由已经变化了,所以在给守卫传参数的时候缓存了一下,之前的路由。

updateRoute (route: Route) {
    const prev = this.current
    this.current = route
    this.cb && this.cb(route)
    this.router.afterHooks.forEach(hook => {
      hook && hook(route, prev)
    })
  }

所以为什么afterEach没有next呢?因为afterEach根本不在迭代器之内,他就没有next来触发迭代器的下一步。

最后我们说一下beforeEach的内容:
我们设置beforeEach全局守卫的时候,守卫们存储在哪里?

beforeEach (fn: Function): Function {
    return registerHook(this.beforeHooks, fn)
}
function registerHook (list: Array, fn: Function): Function {
  list.push(fn)
  // 返回值是一个function
  return () => {
    const i = list.indexOf(fn)
    if (i > -1) list.splice(i, 1)
  }
}

这段代码beforeEach是通过注册守卫的方式,将注册的全局前置守卫放在beforeHooks的容器内,这个容器里面装载着所有的前置守卫

一家人(全局的 前置进入、前置resolve、后置守卫)整整齐齐的放在对应的容器里面,容器是个数组,所以注册全局守卫的时候,是支持注册多个的,

router.beforeEach(()=>{xxx});
router.beforeEach(()=>{yyy});
// 这两个守卫都会执行,只是先注册的先执行,
// registerHook这个方法还可以清除对应的守卫,这个方法也可以使用
总结

我们来回答一下开篇的5个问题

1:导航守卫的执行顺序是怎么样的?

beforeRouteLeave < beforeEach < beforeRouteUpdate < beforeEnter < beforeRouteEnter < beforeResolve < afterEach

2:导航守卫中的next的用处?

next的作用,使导航守卫队列的继续向下迭代

3:为什么afterEach守卫没有next?

afterEach根本不在导航守卫队列内,没有迭代的next

4:beforeEach是否可以叠加?

beforeEach是可以叠加的,所有的全局前置守卫按顺序存放在beforeHooks的数组里面,

5:路由跳转经历了哪几部分?

路由跳转的核心方法是transitionTo,在跳转过程中经历了一次confirmTransition,

(beforeRouteLeave < beforeEach < beforeRouteUpdate < beforeEnter < 异步组件加载)这样顺序的queue为第一个,

在第一个queue迭代完毕后,执行第二个(beforeRouteEnter < beforeResolve)这样顺序的queue,

在执行完毕后,开始执行updateRoute,之后执行全局的afterEach守卫。最后完成路由的跳转。

5个问题解答完毕,希望对你的业务有帮助。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/98088.html

相关文章

  • Vue-router进阶:导航守卫

    摘要:全局前置守卫使用注册一个全局前置守卫。守卫是异步解析执行,此时导航在所有守卫完之前一直处于等待中。中断当前的导航。不过可以通过传一个回调函数来访问组建实例。注意是支持传递回调的唯一守卫。用创建好的实例调用守卫中传给的回调函数。 全局前置守卫 使用router.beforeEach注册一个全局前置守卫。 const router = new VueRouter({...}) router...

    tommego 评论0 收藏0
  • JS每日一题:Vue-router有哪些钩子?使用场景?

    摘要:问有哪些钩子使用场景的实现可以点这里前面我们用大白话讲过什么是钩子,这里在重复一下,就是在什么什么之前什么什么之后英文叫专业点叫生命周期,装逼点可以叫守卫中也存在钩子的概念分为三步记忆全局守卫路由独享守卫组件独享守卫全局守卫很好理解,全 20190218问 Vue-router有哪些钩子?使用场景? router的实现可以点这里 前面我们用大白话讲过什么是钩子,这里在重复一下,就是在...

    张金宝 评论0 收藏0
  • vue-router 一些容易被忽略的知识点

    摘要:调用全局的守卫。在被激活的组件里调用。用创建好的实例调用守卫中传给的回调函数。 本文适用于对 Vue.js 和 vue-router 有一定程度了解的开发者除特殊说明,vue-router 版本为 3.0.2 正文 路由 class 匹配 路由匹配后会给该标签添加 class 属性值 .router-link-active,该功能在嵌套路由中十分方便 class 的实际属性值可以通...

    chunquedong 评论0 收藏0
  • Vue中的验证登录状态

    摘要:用存储用户路由守卫路由中设置的字段就在当中每次跳转的路径登录状态下访问页面会跳到如果没有访问任何页面。一个简单的保存登录状态的小。 Vue项目中实现用户登录及token验证 先说一下我的实现步骤: 使用easy-mock新建登录接口,模拟用户数据 使用axios请求登录接口,匹配账号和密码 账号密码验证后, 拿到token,将token存储到sessionStorage中,并跳转到首...

    draveness 评论0 收藏0
  • 19/3/31学习笔记

    摘要:在失活的组件里调用离开守卫。调用全局的守卫。用创建好的实例调用守卫中传给的回调函数。路由元信息定义路由的时候可以配置字段 vue-router1 router-link 导航到不同组件 Go to Foo Go to Bar 定义路由const routes = [ { path: /foo, component: Foo }, { path: /bar, component: B...

    UnixAgain 评论0 收藏0

发表评论

0条评论

不知名网友

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<