回顾
如果有小伙伴想要回顾之前的知识的话,点击下面的链接查看之前所有的源码分析流程👇
《Vue-Router源码解析:目录》
queue
上一章节我们分析了transitionTo
,在transitionTo
中有这样的一段定义:
const queue: Array<?NavigationGuard> = [].concat(
// in-component leave guards
//在失活组件中调用 beforeRouteLeave
extractLeaveGuards(deactivated),
// global before hooks
//调用 beforeEach
this.router.beforeHooks,
// in-component update hooks
//调用 beforeRouteUpdate
extractUpdateHooks(updated),
// in-config enter guards
//调用激活组件中的 beforeEnter
activated.map((m) => m.beforeEnter),
// async components
resolveAsyncComponents(activated)
)
这边使用了一个空数组去concat
一些其他的数组。这一篇章我们分析的重点就是这个queue
,同时也是VueRouter导航守卫的各种触发时机。
我们先看看extractLeaveGuards(deactivated)
。
beforeRouteLeave
extractLeaveGuards
function extractLeaveGuards(deactivated: Array<RouteRecord>): Array<?Function> {
return extractGuards(deactivated, 'beforeRouteLeave', bindGuard, true)
}
调用了extractGuards
,传入了deactivated
,deactivated
是使用resolveQueue
方法后返回的,是一个失活的路由数组。
进入extractGuards
。
extractGuards
function extractGuards(
records: Array<RouteRecord>,
name: string,
bind: Function,
reverse?: boolean
): Array<?Function> {
const guards = flatMapComponents(records, (def, instance, match, key) => {
const guard = extractGuard(def, name)
if (guard) {
return Array.isArray(guard)
? guard.map((guard) => bind(guard, instance, match, key))
: bind(guard, instance, match, key)
}
})
//beforeRouteLeave 要翻转成先子后父
return flatten(reverse ? guards.reverse() : guards)
}
一开始先调用了flatMapComponents
,传入了deactivated
和一个方法。我们进入flatMapComponents
,进入。
flatMapComponents
export function flatMapComponents(
matched: Array<RouteRecord>,
fn: Function
): Array<?Function> {
return flatten(
matched.map((m) => {
return Object.keys(m.components).map((key) =>
fn(m.components[key], m.instances[key], m, key)
)
})
)
}
先不看flatten
具体实现,我们先看看flatten
的参数,对matched
也就是deactivated
使用map
返回一个数组,这个数组也是由map
生成的,具体生成的逻辑是遍历deactivated
中每个route record
中的components
属性上的所有key
,然后调用回调,传入components[key]
,也就是对应的组件。我们再去看看回调的定义:
(def, instance, match, key) => {
const guard = extractGuard(def, name)
if (guard) {
return Array.isArray(guard)
? guard.map((guard) => bind(guard, instance, match, key))
: bind(guard, instance, match, key)
}
}
首先调用extractGuard
,传入def
(就是组件对象)和name
(beforeRouteLeave
),进入extractGuard
。
extractGuard
function extractGuard(
def: Object | Function,
key: string
): NavigationGuard | Array<NavigationGuard> {
if (typeof def !== 'function') {
// extend now so that global mixins are applied.
def = _Vue.extend(def)
}
return def.options[key]
}
如果def
(也就是组件对象或者方法)不是一个函数的话,就会调用Vue.extend
创建一个组件构造器,之后返回组件上的beforeRouteLeave
方法。
也就是说flatMapComponents
回调中的guard
就是用户定义的beforeRouteLeave
方法。
之后就是调用bind
返回beforeRouteLeave
的方法的调用函数了(此时并没有真正调用beforeRouteLeave
,只是生成一个可以用来触发beforeRouteLeave
的方法而已,具体可以看bind
的定义)。
我们再回到flatMapComponents
中:
export function flatMapComponents(
matched: Array<RouteRecord>,
fn: Function
): Array<?Function> {
return flatten(
matched.map((m) => {
return Object.keys(m.components).map((key) =>
fn(m.components[key], m.instances[key], m, key)
)
})
)
}
flatten
方法其实很简单,就是使用了一个空数组去concat
后面生成的hooks函数数组。
最后这个方法返回的就是一个包含所有激活路由的beforeRouteLeave
钩子函数的数组。所以在queue
中的第一部分就是beforeRouteLeave
钩子。
我们再来看看queue
中的第二部分。
beforeEach
this.router.beforeHooks
const queue: Array<?NavigationGuard> = [].concat(
// in-component leave guards
//在失活组件中调用 beforeRouteLeave
extractLeaveGuards(deactivated),
// global before hooks
//调用 beforeEach
this.router.beforeHooks,
// in-component update hooks
//调用 beforeRouteUpdate
extractUpdateHooks(updated),
// in-config enter guards
//调用激活组件中的 beforeEnter
activated.map((m) => m.beforeEnter),
// async components
resolveAsyncComponents(activated)
)
第二部分就是router
实例上的beforeHooks
实例属性。我们进入class VueRouter
看看this.router.beforeHooks
是如何被赋值的:
class VueRouter{
...
beforeEach(fn: Function): Function {
return registerHook(this.beforeHooks, fn)
}
...
}
function registerHook(list: Array<any>, fn: Function): Function {
list.push(fn)
return () => {
const i = list.indexOf(fn)
if (i > -1) list.splice(i, 1)
}
}
由这两段逻辑可以发现,当用户使用router.beforeEach((to,from,next)=>{})
定义钩子函数的时候,会被VueRouter把所有的beforeEach
都挂载到VueRouter
实例上的this.beforeHooks
实例属性上。所以在queue
中的第二部分就是用户定义的beforeEach
钩子。
beforeRouteUpdate
我们再看看queue
中的第三部分:
const queue: Array<?NavigationGuard> = [].concat(
// in-component leave guards
//在失活组件中调用 beforeRouteLeave
extractLeaveGuards(deactivated),
// global before hooks
//调用 beforeEach
this.router.beforeHooks,
// in-component update hooks
//调用 beforeRouteUpdate
extractUpdateHooks(updated),
// in-config enter guards
//调用激活组件中的 beforeEnter
activated.map((m) => m.beforeEnter),
// async components
resolveAsyncComponents(activated)
)
extractUpdateHooks
,传入了updated
,这边的逻辑和上面的extractLeaveGuards
逻辑一模一样,只不过name
换成了beforeRouteUpdate
,这边就不多说了,有兴趣的同学自己再去过一遍好了。
beforeEnter
第四部分,很简单,就是调用所有activated
组件中定义的beforeEnter
钩子。
异步组件挂载
第五部分,调用了resolveAsyncComponents
,是异步组件的挂载机制,这边我就不详细讲了,因为这不是这边的主要逻辑,有兴趣的同学可以去看看Vue源码中是如何挂载异步组件的。
beforeRouteEnter
这时我们可以看看VueRouter官方的导航守卫流程:
好像还差beforeRouterEnter
、beforeResolve
、afterEach
。这三个导航守卫没有触发。我们先来看看beforeRouterEnter
。
在上一章节:《Vue-Router源码解析:transitionTo》我们提到了runQueue
中有一个回调函数,当时没有细说,这次我们来看看runQueue
的回调函数定义:
runQueue(queue, iterator, () => {
// wait until async components are resolved before
// extracting in-component enter guards
const enterGuards = extractEnterGuards(activated)
//调用 beforeResolve 钩子
const queue = enterGuards.concat(this.router.resolveHooks)
runQueue(queue, iterator, () => {
if (this.pending !== route) {
return abort(createNavigationCancelledError(current, route))
}
this.pending = null
onComplete(route)
if (this.router.app) {
this.router.app.$nextTick(() => {
handleRouteEntered(route)
})
}
})
})
首先调用extractEnterGuards
并传入activated
(激活的路由),前面我们已经使用过extractLeaveGuards
,总体来说两个方法是差不多的,只是传入的name
不一样,这里传入的name
是beforeRouteEnter
。
我们回到runQueue
的回调:
runQueue(queue, iterator, () => {
// wait until async components are resolved before
// extracting in-component enter guards
const enterGuards = extractEnterGuards(activated)
//调用 beforeResolve 钩子
const queue = enterGuards.concat(this.router.resolveHooks)
runQueue(queue, iterator, () => {
if (this.pending !== route) {
return abort(createNavigationCancelledError(current, route))
}
this.pending = null
onComplete(route)
if (this.router.app) {
this.router.app.$nextTick(() => {
handleRouteEntered(route)
})
}
})
})
}
接着刚刚的代码,接下来会执行const queue = enterGuards.concat(this.router.resolveHooks)
,之后又会把enterGuards
和queue
拼接起来继续执行一次runQueue
,不过这次的回调函数不太一样。
所以,这个时候就会触发用户定义在组件内的beforeRouteEnter
钩子(这个钩子访问不到Vue实例,具体可以看看extractEnterGuards
,instance
这个参数传入的是_
)和this.router.resolveHooks
。
beforeResolve
那么这个this.router.resolveHooks
又是什么呢?
我们可以在class VueRouter
中找到this.router.resolveHooks
的定义:
beforeResolve(fn: Function): Function {
return registerHook(this.resolveHooks, fn)
}
所以这个resolveHooks
就是用户定义的beforeResolve
钩子。
afterEach
我们再来看看第二个runQueue
的回调函数:
() => {
if (this.pending !== route) {
return abort(createNavigationCancelledError(current, route))
}
this.pending = null
onComplete(route)
if (this.router.app) {
this.router.app.$nextTick(() => {
handleRouteEntered(route)
})
}
}
在这里面会调用一个onComplete(route)
,方法,这个onComplete
是传进来的参数,往上面一直找的话可以找到onComplete
的定义:
() => {
//导航被确认
this.updateRoute(route)
//调用 onComplete 回调
onComplete && onComplete(route)
this.ensureURL()
//调用 afterEach 钩子
this.router.afterHooks.forEach((hook) => {
hook && hook(route, prev)
})
// fire ready cbs once
if (!this.ready) {
this.ready = true
this.readyCbs.forEach((cb) => {
cb(route)
})
}
},
在这先会确认导航,然后调用transitionTo
传入的完成回调,之后会遍历afterHooks
然后挨个调用,这个afterHooks
我们可以看class VueRouter
:
afterEach(fn: Function): Function {
return registerHook(this.afterHooks, fn)
}
所以这个afterHooks
就是用户定义的afterEach
钩子。
总结
所以根据源码可以看出Vue导航守卫触发的顺序为:beforeRouteLeave
=> beforeEach
=> beforeRouteUpdate
=> beforeEnter
=> beforeRouteEnter
=> beforeResolve
=> afterEach