1. vue-router导航守卫解析流程
vue-router官网的解析流程如下:
- 导航被触发。
- 在失活的组件里调用
beforeRouteLeave
守卫。 - 调用全局的
beforeEach
守卫。 - 在重用的组件里调用
beforeRouteUpdate
守卫(2.2+)。 - 在路由配置里调用
beforeEnter
。 - 解析异步路由组件。
- 在被激活的组件里调用
beforeRouteEnter
。 - 调用全局的
beforeResolve
守卫(2.5+)。 - 导航被确认。
- 调用全局的
afterEach
钩子。 - 触发 DOM 更新。
- 调用
beforeRouteEnter
守卫中传给next
的回调函数,创建好的组件实例会作为回调函数的参数传入。
现在看看源码里是怎么实现这个流程的。
2. 源码
1.push方法
router 的push代码如下,调用了pushWithRedirect
方法
function push(to: RouteLocationRaw) {
return pushWithRedirect(to)
}
2.pushWithRedirect
删除了目前用不到的逻辑,精简的代码如下:
// 路由跳转方法
function pushWithRedirect(
to: RouteLocationRaw | RouteLocation,
redirectedFrom?: RouteLocation
): Promise<NavigationFailure | void | undefined> {
// 对to参数进行解析,返回一个目标路由
const targetLocation: RouteLocation = (pendingLocation = resolve(to))
// 发起路由
// currentRoute 是一个 shallowRef
const from = currentRoute.value
const data: HistoryState | undefined = (to as RouteLocationOptions).state
const force: boolean | undefined = (to as RouteLocationOptions).force
// to could be a string where `replace` is a function
const replace = (to as RouteLocationOptions).replace === true
const shouldRedirect = handleRedirectRecord(targetLocation)
...
// 如果失败了就返回Promise也还会调用afterEach
// 如果成功就调用navigate,naviagete中会调用路由守卫,navigate也会返回一个Promise
return (failure ? Promise.resolve(failure) : navigate(toLocation, from))
.catch((error: NavigationFailure | NavigationRedirectError) =>
...
)
.then((failure: NavigationFailure | NavigationRedirectError | void) => {
if (failure) {
if (
isNavigationFailure(failure, ErrorTypes.NAVIGATION_GUARD_REDIRECT)
) {
if (
__DEV__ &&
...
) {
...
}
return pushWithRedirect(
// keep options
assign(
{
// preserve an existing replace but allow the redirect to override it
replace,
},
locationAsObject(failure.to),
{
state: data,
force,
}
),
// preserve the original redirectedFrom if any
redirectedFrom || toLocation
)
}
} else {
// if we fail we don't finalize the navigation
failure = finalizeNavigation(
toLocation as RouteLocationNormalizedLoaded,
from,
true,
replace,
data
)
}
// 执行afterEach守卫
triggerAfterEach(
toLocation as RouteLocationNormalizedLoaded,
from,
failure
)
return failure
})
}
可以看到该方法返回一个Promise
,但是具体返回的内容根据failure
变量决定。
若failure
为true,则说明在路由的解析过程中已经出现问题,不会再执行除afterEach
以外的导航守卫。
若failure
为false,则会执行navigate(toLocation, from)
,而导航守卫会在该方法中执行。
3. navigate 方法
function navigate(
to: RouteLocationNormalized,
from: RouteLocationNormalizedLoaded
): Promise<any> {
let guards: Lazy<any>[]
// 三个组件导航守卫
const [leavingRecords, updatingRecords, enteringRecords] =
extractChangingRecords(to, from)
// all components here have been resolved once because we are leaving
guards = extractComponentsGuards(
// 反转leavingRecords数组
leavingRecords.reverse(),
'beforeRouteLeave',
to,
from
)
// leavingRecords is already reversed
// 在这保证push后的顺序
for (const record of leavingRecords) {
record.leaveGuards.forEach(guard => {
guards.push(guardToPromiseFn(guard, to, from))
})
}
const canceledNavigationCheck = checkCanceledNavigationAndReject.bind(
null,
to,
from
)
guards.push(canceledNavigationCheck)
// run the queue of per route beforeRouteLeave guards
return (
// 1. 执行 beforeRouteLeave 导航守卫
runGuardQueue(guards)
.then(() => {
// 2. 执行 beforeEach 导航守卫
// check global guards beforeEach
guards = []
for (const guard of beforeGuards.list()) {
guards.push(guardToPromiseFn(guard, to, from))
}
guards.push(canceledNavigationCheck)
return runGuardQueue(guards)
})
.then(() => {
// 3. 执行 beforeRouteUpdate 导航守卫
// check in components beforeRouteUpdate
guards = extractComponentsGuards(
updatingRecords,
'beforeRouteUpdate',
to,
from
)
for (const record of updatingRecords) {
record.updateGuards.forEach(guard => {
guards.push(guardToPromiseFn(guard, to, from))
})
}
guards.push(canceledNavigationCheck)
// run the queue of per route beforeEnter guards
return runGuardQueue(guards)
})
.then(() => {
// 4.执行 beforeEnter 导航守卫
// check the route beforeEnter
guards = []
for (const record of to.matched) {
// do not trigger beforeEnter on reused views
if (record.beforeEnter && !from.matched.includes(record)) {
if (isArray(record.beforeEnter)) {
for (const beforeEnter of record.beforeEnter)
guards.push(guardToPromiseFn(beforeEnter, to, from))
} else {
guards.push(guardToPromiseFn(record.beforeEnter, to, from))
}
}
}
guards.push(canceledNavigationCheck)
// run the queue of per route beforeEnter guards
return runGuardQueue(guards)
})
.then(() => {
// NOTE: at this point to.matched is normalized and does not contain any () => Promise<Component>
// clear existing enterCallbacks, these are added by extractComponentsGuards
to.matched.forEach(record => (record.enterCallbacks = {}))
// 5.执行 beforeRouteEnter 导航守卫
// check in-component beforeRouteEnter
guards = extractComponentsGuards(
enteringRecords,
'beforeRouteEnter',
to,
from
)
guards.push(canceledNavigationCheck)
// run the queue of per route beforeEnter guards
return runGuardQueue(guards)
})
.then(() => {
// 6.执行 beforeResolve 导航守卫
// check global guards beforeResolve
guards = []
for (const guard of beforeResolveGuards.list()) {
guards.push(guardToPromiseFn(guard, to, from))
}
guards.push(canceledNavigationCheck)
return runGuardQueue(guards)
})
// catch any navigation canceled
.catch(err =>
isNavigationFailure(err, ErrorTypes.NAVIGATION_CANCELLED)
? err
: Promise.reject(err)
)
)
}
可以看到在navigate
中是利用Promise.then链式调用实现的解析流程先后顺序。