导航解析流程
- 在被激活的组件里调用 beforeRouteEnter。
- 调用全局的 beforeResolve 守卫 (2.5+)。
- 导航被确认。
- 调用全局的 afterEach 钩子。
- 触发 DOM 更新。
- 调用 beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入。
runQueue函数第一次执行完毕,导航守卫的前6步就执行完毕了,之后会触发传入的第三个回调函数。回调函数首先执行const enterGuards = extractEnterGuards(activated),extractEnterGuards(activated)会拿到activated数组中的record中的beforeRouteEnter。为什么要在6.解析异步路由组件之后执行beforeRouteEnter,是因为activated是新创建的组件,他很有可能是一个异步组件,如果不进行第6步解析,是拿不到他定义的beforeRouteEnter钩子。在执行extractGuards的时候,传入的第三个参数,并不是bindGuard函数,而是(guard, _, match, key) => { return bindEnterGuard(guard, match, key) }。
-
bindGuard函数在执行runQueue函数的时候,会执行guard.apply(instance, arguments),instance是当前的vuecomponent,也就是说在执行beforeRouteLeave和beforeRouteUpdate的时候,this被指到了当前的vuecomponent上,所以可以在这两个导航守卫钩子中访问到this。 -
bindEnterGuard函数在执行runQueue函数的时候,会判断在调用next的时候,是否传入了函数,如果传入了函数,那么会执行match.enteredCbs[key].push(cb),官方的描述是无法在beforeRouteEnter中访问到this,因为此时路由的跳转并没有被确认,当执行了next,确认跳转之后,会创建组件实例,然后会通知next中传入的函数来访问到this。最终会通过调用handleRouteEntered函数来调用传入的cbs,并把record.instances[name]作为参数传入(也就是组件实例),调用handleRouteEntered是在RouterView组件中执行data.hook.init也就是组件初始化之后,进行回调函数的调用。
// src/history/base.js
runQueue(queue, iterator, () => {
// wait until async components are resolved before
// extracting in-component enter guards
const enterGuards = extractEnterGuards(activated);
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(() => {
console.log(444)
handleRouteEntered(route);
});
}
});
});
...
function extractEnterGuards (
activated: Array<RouteRecord>
): Array<?Function> {
return extractGuards(
activated,
'beforeRouteEnter',
(guard, _, match, key) => {
return bindEnterGuard(guard, match, key)
}
)
}
function bindEnterGuard (
guard: NavigationGuard,
match: RouteRecord,
key: string
): NavigationGuard {
return function routeEnterGuard (to, from, next) {
return guard(to, from, cb => {
if (typeof cb === 'function') {
if (!match.enteredCbs[key]) {
match.enteredCbs[key] = []
}
match.enteredCbs[key].push(cb)
}
next(cb)
})
}
}
拿到activated中的beforeRouteEnter后,会和this.router.resolveHooks进行concat最终成为新的queue,也就是router实例的beforeResolve钩子也就是8.调用全局的 beforeResolve 守卫 (2.5+)
beforeResolve (fn) {
return registerHook(this.resolveHooks, fn)
}
拿到新的queue,会去执行runQueue函数,执行完毕后触发回调函数,触发onComplete(route),也就是在最初执行confirmTransition传入的第二个参数,第二个参数是一个回调函数,首先他会执行updateRoute函数,把this.current置为route,这样就完成了当前路径的一个切换,也就是9. 导航被确认,执行完updateRoute后会遍历this.router.afterHooks,也就是全局的afterEach钩子,这对应了10. 调用全局的 afterEach 钩子。更新dom在之后会专门抽一篇文章去分析。最后调用 beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入。这一步在v3.4.9版本中,调用的时机已经被修改为了router-view中创建组件实例之后。最后在nextTick之后会再执行一次handleRouteEntered,这块目前没有用到,可能是为了防止某些意外的情况,导致钩子的next中的函数未被执行。
this.confirmTransition(
route,
() => {
this.updateRoute(route);
onComplete && onComplete(route);
this.ensureURL();
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);
});
}
},
err => {
if (onAbort) {
onAbort(err);
}
if (err && !this.ready) {
// Initial redirection should not mark the history as ready yet
// because it's triggered by the redirection instead
// https://github.com/vuejs/vue-router/issues/3225
// https://github.com/vuejs/vue-router/issues/3331
if (!isNavigationFailure(err, NavigationFailureType.redirected) || prev !== START) {
this.ready = true;
this.readyErrorCbs.forEach(cb => {
cb(err);
});
}
}
}
);