首頁 >web前端 >js教程 >帶你徹底搞定vue-Router的導航守衛

帶你徹底搞定vue-Router的導航守衛

不言
不言轉載
2018-09-30 15:58:343968瀏覽

這篇文章帶給大家的內容是關於帶你徹底搞定vue-Router的導航守衛,有一定的參考價值,有需要的朋友可以參考一下,希望對你有幫助。

vue-router導航守衛

在本期文章中,我將為大家梳理弄明白以下幾個事情,

1:導航守衛的執行順序是怎麼樣的?

2:導航守衛中的next的用處?

3:為什麼afterEach守衛沒有next?

4:beforeEach是否可以疊加?

5:路由跳轉經歷了哪幾部分?

在之前說過的一個內容router實例的history屬性幫助我們做了所有跳躍部分的事情,所以導航守衛的內容也在history中。

帶你徹底搞定vue-Router的導航守衛

我們以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的導航守衛

#這個過程中的困難點是什麼?

如何收集守衛組合成守衛隊列?

如何依序執行守衛的同時可以隨時中止守衛佇列?

如何找到守衛佇列執行完畢後的那個節點(守衛佇列執行完可以通知一下)

在vue-router中封裝了一個runQueue函數來解決上面的三個問題的後兩個。第一個問題呢則牽涉到vue-router處理路由的一個大篇章,我們著重講一下runQueue函數

runQueue函數的想法:

1:迭代器模式來確保遍歷佇列時每一步都是可控的,

2:佇列完成後執行對應的回呼函數,

#推斷出函數參數的對應功能:

queue : 需要執行的守衛佇列

fn    : 迭代器函數,守衛佇列的每個守衛都去執行迭代器函數

fn的第二個參數讓迭代器進入下一步,不掉用就不會進入下一步(很重點)

cb    : 結束時呼叫的回呼函數

export function runQueue (queue: Array<?NavigationGuard>, 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是怎麼幫助我們解決守衛佇列處理的問題就算說完了。

(快留起來,這函數簡直吊極了!)

處理守衛隊列的大錘子我們已經製造好了,可以開工了,那你的守衛隊列呢? ?

對對對,還有守衛隊列要收集。
這時候我們要想想有哪些守衛?

守衛有兩大種類:前置守衛、後置守衛。

  • 前置守衛:

  1. 全域的前置守衛: beforeEach beforeResolve

  2. #路由獨享的守衛: beforeEnter

  3. 元件內的守衛:    beforeRouterEnter、beforeRouterUpdate、beforeRouteLeave

    1. #11

    2. ##全域的後置守衛: afterEach

    3. #我們要想這些守衛都是怎麼註冊的,

    在路由實例註冊的:

    beforeEach、beforeResolve、afterEach

    #########在路由配置中註冊的(路由獨享守衛):######beforeEnter############元件內的路由守衛:######beforeRouteLeave、beforeRouteUpdate、beforeRouteEnter#############好了我們要去榨取對應的守衛了,######confirmTransition的守衛分成兩個隊列:我們先來看第一個隊列###
     // 拿到路由跳转中更新、摧毁、激活时对应展示的组件。
     const {
          updated,
          deactivated,
          activated
        } = resolveQueue(this.current.matched, route.matched)
        // 路由守卫
        const queue: Array<?NavigationGuard> = [].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。 ######再下一步要有一個處理守衛的迭代器:###

    我们该如何处理守卫?

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

    2. 保证守卫可以正常通过,

    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:

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

    2. 全局的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<any>, fn: Function): Function {
      list.push(fn)
      // 返回值是一个function
      return () => {
        const i = list.indexOf(fn)
        if (i > -1) list.splice(i, 1)
      }
    }</any>

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

    帶你徹底搞定vue-Router的導航守衛

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

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

    总结

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

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

    beforeRouteLeave

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

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

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

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

    4:beforeEach是否可以叠加?

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

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

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

    (beforeRouteLeave

    在第一个queue迭代完毕后,执行第二个(beforeRouteEnter

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

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


    以上是帶你徹底搞定vue-Router的導航守衛的詳細內容。更多資訊請關注PHP中文網其他相關文章!

    陳述:
    本文轉載於:segmentfault.com。如有侵權,請聯絡admin@php.cn刪除