vue-router 导航守卫中 next 控制实现

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 源码这块的实现方式,相信你会豁然开朗。

分享本文至:

日历

链接

blogger

蓝蓝 http://www.lanlanwork.com

存档