vue-router 導航守衛中 next 控制實現

使用 vue-router 的導航守衛鉤子函數,某些鉤子函數可讓開發者根據業務邏輯,控制是否進行下一步,或者進入到指定的路由。javascript

例如,後臺管理頁面,會在進入路由前,進行必要登陸、權限判斷,來決定去往哪一個路由,如下是僞代碼:前端

// 全局導航守衛
router.beforEach((to, from, next) => {
  if('no login'){
    next('/login')
  }else if('admin') {
    next('/admin')
  }else {
    next()
  }
})

// 路由配置鉤子函數
{
  path: '',
  component: component,
  beforeEnter: (to, from, next) => {
    next()
  }
}

// 組件中配置鉤子函數
{
  template: '',
  beforeRouteEnter(to, from, next) {
    next()
  }
}

調用 next,意味着繼續進行下面的流程;不調用,則直接終止,致使路由中設置的組件沒法渲染,會出現頁面一片空白的現象。vue

鉤子函數有不一樣的做用,例如 beforEachafterEachbeforeEnterbeforeRouteEnterbeforeRouteUpdatebeforeRouteLeave,針對這些註冊的鉤子函數,要依次進行執行,而且在必要環節有控制權決定是否繼續進入到下一個鉤子函數中。java

如下分析下源碼中實現的方式,而源碼中處理的邊界狀況比較多,須要抓住核心點,去掉冗餘代碼,精簡出便於理解的實現。git

精簡源碼核心功能

總結下核心點:鉤子函數註冊的回調函數,能順序執行,同時會將控制權交給開發者。github

先來一個可以註冊回調函數的類:vue-router

class VueRouter {
  constructor(){
    this.beforeHooks = []
    this.beforeEnterHooks = []

    this.afterHooks = []
  }

  beforEach(callback){
    return registerHook(this.beforeHooks, callback)
  }
  beforeEnter(callback){
    return registerHook(this.beforeEnterHooks, callback)
  }
  afterEach(callback){
    return registerHook(this.afterHooks, callback)
  }
}
function registerHook (list, fn) {
  list.push(fn)
  return () => {
    const i = list.indexOf(fn)
    if (i > -1) list.splice(i, 1)
  }
}

聲明的類,提供了 beforEachbeforeEnterafterEach 來註冊必要的回調函數。函數

抽象出一個 registerHook 公共方法,做用:學習

  1. 註冊回調函數
  2. 返回的函數,能夠取消註冊的回調函數

使用一下:this

const router = new VueRouter()

const beforEach = router.beforEach((to, from, next) => {
  console.log('beforEach');
  next()
})
// 取消註冊的函數
beforEach()

以上的回調函數會被取消,意味着不會執行了。

router.beforEach((to, from, next) => {
  console.log('beforEach');
  next()
})

router.beforeEnter((to, from, next) => {
  console.log('beforeEnter');
  next()
})

router.afterEach(() => {
  console.log('afterEach');
})

以上註冊的鉤子函數會依次執行。beforEachbeforeEnter 的回調接收內部傳來的參數,同時經過調用 next 可繼續走下面的回調函數,若是不調用,則直接被終止了。
最後一個 afterEach 在上面的回調函數都執行後,才被執行,且不接收任何參數。

先來實現依次執行,這是最簡單的方式,在類中增長 run 方法,手動調用:

class VueRouter {
  // ... 其餘省略,增長 run 函數

  run(){
    // 把須要依次執行的回調存放在一個隊列中
    let queue = [].concat(
      this.beforeHooks,
      this.afterHooks 
    )
    
    for(let i = 0; i < queue.length; i++){
      if(queue(i)) {
        queue(i)('to', 'from', () => {})
      }
    }
  }
}

// 手動調用 

router.run()

打印:

'beforEach'
'beforeEnter'

上面把要依次執行的回調函數聚合在一個隊列中執行,並傳入必要的參數,但這樣開發者不能控制是否進行下一步,即使不執行 next 函數,依然會依次執行完隊列的函數。

改進一下:

class VueRouter {
  // ... 其餘省略,增長 run 函數 

  run(){
    // 把須要依次執行的回調存放在一個隊列中
    let queue = [].concat(
      this.beforeHooks,
      this.afterHooks 
    )
    queue[0]('to', 'from', () => {
      queue[1]('to', 'from', () => {
       console.log('調用結束');
      })
    })
  }
}

router.beforEach((to, from, next) => {
  console.log('beforEach');
  // next()
})

router.beforeEnter((to, from, next) => {
  console.log('beforeEnter');
  next()
})

傳入的 next 函數會有調用下一個回調函數的行爲,把控制權交給了開發者,調用了 next 函數會繼續執行下一個回調函數;不調用 next 函數,則終止了隊列的執行,因此打印結果是:

'beforEach'

上面實現有個弊端,代碼不夠靈活,手動一個個調用,在真實場景中沒法肯定註冊了多少個回調函數,因此須要繼續抽象成一個功能更強的方法:

function runQueue (queue, fn, cb) {
  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. 第一個參數爲回調函數隊列, 會依次取出來;
  2. 第二個參數是函數,它接受隊列中的函數,進行一些其餘處理;並能進行下個回調函數的執行;
  3. 第三個參數是隊列執行結束後調用。

知道了這個函數的含義,來使用一下:

class VueRouter {
  // ... 其餘省略,增長 run 函數 

  run(){
    // 把須要依次執行的回調存放在一個隊列中
    let queue = [].concat(
      this.beforeHooks,
      this.beforeEnterHooks 
    )

    // 接收回到函數,和進行下一個的執行函數
    const iterator = (hook, next) => {
      // 傳給回調函數的參數,第三個參數是函數,交給開發者調用,調用後進行下一個
      hook('to', 'from', () => {
        console.log('執行下一個回調時,處理一些相關信息');
        next()
      })
    }

    runQueue(queue, iterator, () => {

      console.log('執行結束');
      // 執行 afterEach 中的回調函數
      this.afterHooks.forEach((fn) => {
        fn()
      })
    })
  }
}
// 註冊
router.beforEach((to, from, next) => {
  console.log('beforEach');
  next()
})

router.beforeEnter((to, from, next) => {
  console.log('beforeEnter');
  next()
})

router.afterEach(() => {
  console.log('afterEach');
})

router.run();

從上面代碼能夠看出來,每次把隊列 queue 中的回調函數傳給 iterator , 用 hook 接收,並調用。
傳給 hook 必要的參數,尤爲是第三個參數,開發者在註冊的回調函數中調用,來控制進行下一步。
在隊列執行完畢後,依次執行 afterHooks 的回調函數,不傳入任何參數。

因此打印結果爲:

beforEach
執行下一個回調時,處理一些相關信息
beforeEnter
執行下一個回調時,處理一些相關信息
執行結束
afterEach

以上實現的很是巧妙,再看 Vue-router 源碼這塊的實現方式,相信你會豁然開朗。

以上若有誤差歡迎指正學習,謝謝。~~~~

github博客地址:https://github.com/WYseven/blog,歡迎star。

若是對你有幫助,請關注【前端技能解鎖】:
qrcode_for_gh_d0af9f92df46_258.jpg

相關文章
相關標籤/搜索