持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第22天,点击查看活动详情
前言
大家好,上一篇文章VueRouter构造函数中我们对VueRouter的构造函数进行了分析解读,了解到在创建VueRouter实例的同时还创建了路由的映射,定义了动态添加路由及获取路由的方法,处理了路由模式等等。接下来我们将一起来学习VueRouter为我们提供的一个路由导航守卫 - 全局前置守卫beforeEach。我们还是从它的用法开始。
全局前置守卫beforeEach
在router实例上有一个beforeEach方法,我们可以借助该方法来注册一个全局前置守卫,所谓的前置守卫可以理解为一个路由拦截器,也就是说所有的路由在跳转前都要先被前置守卫拦截,就像一个大筛子一样所有的路由都要过一遍筛子才能继续下去。这样我们就可以借助这个"筛子"做一些其它的操作,比如登录校验,在每次跳转到新页面前都要先检测一下用户有没有登录,或者有没有访问该页面的权限等,都可以在这个前置守卫中进行过滤。
前置守卫的使用也很简单,只需要调用路由实例上的beforeEach方法,并传递一个包含3个参数的回调函数即可,然后在回调函数中就可以做一些想做的事了。
router.beforeEach((to, from, next)=>{
//...
});
当一个导航触发时,全局前置守卫按照创建顺序调用。守卫是异步解析执行,此时导航在所有守卫 resolve 完之前一直处于 等待中。
每个守卫方法接收三个参数:
- to:是一个路由对象,表示要进入的路由
- from:也是一个路由对象,表示当前要离开的路由(或者说从哪个路由过来的)
- next:next是一个函数,在前置守卫中必须要调用这个方法,否则不会进入到新页面。该方法在调用时有如下几种传参方式
- next(),直接调用,不传任何值,这种情况会直接进行管道中的下一个钩子
- next(false),传递一个false,直接中断当前路由
- next('/')或next({path:'/'}),跳转到对应的路由地址
源码解读
上面简单介绍了路由前置守卫的用法及作用,接下来我们解读一下它的源码,看看它是如何实现异步解析又是如何实现守卫的。首先我们先来看下下面的代码调试图及beforeEach的源码
VueRouter.prototype.beforeEach = function beforeEach (fn) {
return registerHook(this.beforeHooks, fn)
};
function registerHook (list, fn) {
list.push(fn);
return function () {
var i = list.indexOf(fn);
if (i > -1) { list.splice(i, 1); }
}
}
var queue = [].concat(
...
this.router.beforeHooks,
...
);
runQueue(queue, iterator, function () {
// wait until async components are resolved before
// extracting in-component enter guards
var enterGuards = extractEnterGuards(activated);
var queue = enterGuards.concat(this$1.router.resolveHooks);
runQueue(queue, iterator, function () {
if (this$1.pending !== route) {
return abort(createNavigationCancelledError(current, route))
}
this$1.pending = null;
onComplete(route);
if (this$1.router.app) {
this$1.router.app.$nextTick(function () {
handleRouteEntered(route);
});
}
});
});
function runQueue (queue, fn, cb) {
var step = function (index) {
if (index >= queue.length) {
cb();
} else {
if (queue[index]) {
fn(queue[index], function () {
step(index + 1);
});
} else {
step(index + 1);
}
};
step(0);
}
- 我们看到beforeEach的源码非常简单,仅仅调用了一个registerHook方法,并传递了this.beforeHooks和fn作为实参
- beforeHooks是路由实例上的一个数组类型的属性
- fn就是我们在调用beforeEach时传给它的包含to、from和next三个参数的回调函数
- 再来看registerHook方法,该方法也很简单,其实最核心就做了一件事:就是将fn添加到beforeHooks数组中保存起来(该方法中的list就是从外面传进来的路由实例上的beforeHook)。
- 然后registerHook方法的返回值也是一函数,在该函数中所做的事就是fn再从beforeHooks数组中删除出去
到这里其实关于beforeEach的源码就已经解读完毕了,但是我们好像解读了个寂寞,好像解读了也好像什么也没解读,到这里我们只知道befroeEach中的回调函数被添加到了beforeHooks数组中保存了起来,但是具体什么时候调用的,又是怎么调用的我们还一无所知,于是决定打开浏览器调试一番,别说还真有发现:
原来这个beforeHooks会被添加到一个名为queue的队列中,然后再通过runQueue方法将队列中的方法一一执行。
下面简单梳理下大概的执行流程 - 首先在Vue调用init进行初始化时会调用beforeCreate钩子函数。前面我们在解读vuex的install方法时,不知道大家还记不记得
在全局混入beforeCreate时,在根组件Vue的beforeCreate方法中会调用路由实例的init方法
- 在路由实例的init方法中调用了transationTo方法,在该方法中又调用了confirmTransaction方法,最终在这里执行了runQueue方法,在前面我们提到这个方法会把队列queue中的所有函数调用执行,其中就包括beforeEach的回调函数fn
- 那么进入到runQueue方法中我们看到:
- runQueue接收三个参数:queue待执行的队列,fn 是外面定义的一个iterator函数,cb是一个回调函数
- 在runQueue内部又定义并执行了step函数,第一次调用传递参数0
- 在step中首先判断传进来的索引是否大于等于队列queue的长度,如果大于queue的长度则说明队列中的方法分已被执行完毕,这时就直接调用回调函数cb执行
- 如果索引小于队列长度则将队列中对应索引的方法取出来并传给fn(前面定义的iterator)函数执行。注意这里只是让iterator函数执行,而不是真正的队列queue中的函数执行。至于队列queue中的方法最终是在这个iterator中被调用,进而也就执行到了beforeEach中的回调函数了
总结
本次我们分享了路由前置守卫的用法及用途,并对其实现原理进行了解读,简单概括为:
在beforeEach中调用了registerHook函数,并在该函数中将beforeEach的回调函数保存在路由实例的beforeHooks数组中。当根组件Vue的beforeCreate钩子函数执行时会调用到路由实例的init方法,在该方法中进行一系列调用后最终会执行到runQueue方法,然后通过该方法将队列中的方法一一调用执行
关于路由前置守卫的源码就分享到这里了,欢迎大佬们指点和补充!!!