使用 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。
若是对你有帮助,请关注【前端技能解锁】: