使用 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
鉤子函數有不一樣的做用,例如 beforEach,afterEach,beforeEnter,beforeRouteEnter,beforeRouteUpdate,beforeRouteLeave,針對這些註冊的鉤子函數,要依次進行執行,而且在必要環節有控制權決定是否繼續進入到下一個鉤子函數中。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) } }
聲明的類,提供了 beforEach 、beforeEnter 和 afterEach 來註冊必要的回調函數。函數
抽象出一個 registerHook 公共方法,做用:學習
使用一下: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'); })
以上註冊的鉤子函數會依次執行。beforEach 和 beforeEnter 的回調接收內部傳來的參數,同時經過調用 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 就是執行隊列的通用方法。
知道了這個函數的含義,來使用一下:
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。
若是對你有幫助,請關注【前端技能解鎖】: