2020-5-14 seo達人
使用 vue-router 的導航守衛鉤子函數,某些鉤子函數可以讓開發者根據業務邏輯,控制是否進行下一步,或者進入到指定的路由。
例如,后臺管理頁面,會在進入路由前,進行必要登錄、權限判斷,來決定去往哪個路由,以下是偽代碼:
// 全局導航守衛
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,意味著繼續進行下面的流程;不調用,則直接終止,導致路由中設置的組件無法渲染,會出現頁面一片空白的現象。
鉤子函數有不同的作用,例如 beforEach,afterEach,beforeEnter,beforeRouteEnter,beforeRouteUpdate,beforeRouteLeave,針對這些注冊的鉤子函數,要依次進行執行,并且在必要環節有控制權決定是否繼續進入到下一個鉤子函數中。
以下分析下源碼中實現的方式,而源碼中處理的邊界情況比較多,需要抓住核心點,去掉冗余代碼,精簡出便于理解的實現。
精簡源碼核心功能
總結下核心點:鉤子函數注冊的回調函數,能順序執行,同時會將控制權交給開發者。
先來一個能夠注冊回調函數的類:
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 公共方法,作用:
注冊回調函數
返回的函數,可以取消注冊的回調函數
使用一下:
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 源碼這塊的實現方式,相信你會豁然開朗。