继上一遍文章大概介绍了vue-router里面的概念,这一篇文章主要详细介绍路由跳转中发生了什么。vue
路由跳转执行的代码主要在./base.js
文件里,详细看transitionTo
方法。vue-router
transitionTo (location: RawLocation, onComplete?: Function, onAbort?: Function) {
const route = this.router.match(location, this.current)
this.confirmTransition(route, () => {
this.updateRoute(route)
onComplete && onComplete(route)
this.ensureURL()
// 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) {
this.ready = true
this.readyErrorCbs.forEach(cb => { cb(err) })
}
})
}
复制代码
transitionTo
代码很是简单,执行this.route.match
,经过比较新的路由和就得路由拿到一个route
对象,而后执行confirmTransition
确认路由跳转。bash
下面看看这个route
对象的定义app
declare type Route = {
path: string;
name: ?string;
hash: string;
query: Dictionary<string>;
params: Dictionary<string>;
fullPath: string;
matched: Array<RouteRecord>;
redirectedFrom?: string;
meta?: any;
}
复制代码
对照代码,咱们主要看matched
这个属性,在上一篇文章里面已经介绍过RouteRecord
对象的定义。路由一开始会执行createMatcher
生成一个路由映射表,所以matched
里面存放就是咱们将要跳转到的路由匹配上的路由配置对象。异步
举一个简单的例子说明:async
[{
path: '/parent',
component: Parent,
children: [
{ path: 'foo', component: Foo },
{ path: 'bar', component: Bar },
],
}]
复制代码
假设咱们配置了如下的路由,createMatcher
会生成根据path创建的映射表函数
pathMap = {
'/parent': RouteRecord,
'/parent/foo': RouteRecord,
'/parent/bar': RouteRecord,
}
复制代码
假如咱们发生了一个从/parent
路由跳转到/parent/foo
路由,首先执行如下代码生成的route
对象post
const route = this.router.match(location, this.current)
复制代码
所以根据咱们假设的配置,这里的route
里的matched
将会包含/parent
和/parent/foo
的RouteRecord
。至于具体的match
方法代码就不详细解释了。ui
继续讲路由的跳转,生成route
对象后将会执行一个确认的方法confirmTransition
。this
因为这个方法代码比较长,咱们拆开来讲明,首先看这个方法的入参说明,接受三个参数,route
对象在前面已经生成过了,另外两个是执行完成的回调方法和退出的回调方法。
confirmTransition (route: Route, onComplete: Function, onAbort?: Function)
复制代码
在代码的一开始首先判断当前路由与跳转的路由是不是同一个路由,若是是则直接退出。
const current = this.current
const abort = err => {
if (isError(err)) {
if (this.errorCbs.length) {
this.errorCbs.forEach(cb => { cb(err) })
} else {
warn(false, 'uncaught error during route navigation:')
console.error(err)
}
}
onAbort && onAbort(err)
}
if (
isSameRoute(route, current) &&
// in the case the route map has been dynamically appended to
route.matched.length === current.matched.length
) {
this.ensureURL()
return abort()
}
复制代码
这里的ensureURL
方法定义在HTML5History
的原型链上,实际上执行的是保存路由变化历史记录,根据push
是true
或false
来肯定执行pushState
仍是replaceState
。这一方法在执行完路由跳转后一样会执行一次。
HTML5History.prototype.ensureURL = function ensureURL (push) {
if (getLocation(this.base) !== this.current.fullPath) {
var current = cleanPath(this.base + this.current.fullPath);
push ? pushState(current) : replaceState(current);
}
};
复制代码
继续看后面的代码,首先经过resolveQueue
对比this.current
和route
对象的matched
提取三种变化的组件队列。根据命名咱们直接可得知updated
、deactivated
、activated
分别对应更新的组件、失效的组件、激活的组件。而后生成一个须要执行方法的队列queue
,根据这个队列的生成定义,咱们能够看出执行方法的顺序,至于经过extractLeaveGuards
和extractUpdateHooks
方法提取组件里的守卫函数就不细说了。
在失活的组件里调用离开守卫。
调用全局的 beforeEach 守卫。
在重用的组件里调用 beforeRouteUpdate 守卫
在激活的路由配置里调用 beforeEnter。
解析异步路由组件。
const {
updated,
deactivated,
activated
} = resolveQueue(this.current.matched, route.matched)
const queue: Array<?NavigationGuard> = [].concat(
// in-component leave guards
extractLeaveGuards(deactivated),
// global before hooks
this.router.beforeHooks,
// in-component update hooks
extractUpdateHooks(updated),
// in-config enter guards
activated.map(m => m.beforeEnter),
// async components
resolveAsyncComponents(activated)
)
复制代码
看看resolveQueue
是如何提取变化的组件。比较current
和next
肯定一个变化的位置i
,next
里的从0
到i
则是updated
的部分,i
以后的则是activated
的部分,而current
里i
以后的则是deactivated
的部分。
function resolveQueue (
current: Array<RouteRecord>,
next: Array<RouteRecord>
): {
updated: Array<RouteRecord>,
activated: Array<RouteRecord>,
deactivated: Array<RouteRecord>
} {
let i
const max = Math.max(current.length, next.length)
for (i = 0; i < max; i++) {
if (current[i] !== next[i]) {
break
}
}
return {
updated: next.slice(0, i),
activated: next.slice(i),
deactivated: current.slice(i)
}
}
复制代码
接下来就是生成迭代器方法iterator
,执行runQueue
方法。
this.pending = route
const iterator = (hook: NavigationGuard, next) => {
if (this.pending !== route) {
return abort()
}
try {
hook(route, current, (to: any) => {
if (to === false || isError(to)) {
// next(false) -> abort navigation, ensure current URL
this.ensureURL(true)
abort(to)
} else if (
typeof to === 'string' ||
(typeof to === 'object' && (
typeof to.path === 'string' ||
typeof to.name === 'string'
))
) {
// next('/') or next({ path: '/' }) -> redirect
abort()
if (typeof to === 'object' && to.replace) {
this.replace(to)
} else {
this.push(to)
}
} else {
// confirm transition and pass on the value
next(to)
}
})
} catch (e) {
abort(e)
}
}
runQueue(queue, iterator, () => {
const postEnterCbs = []
const isValid = () => this.current === route
// wait until async components are resolved before
// extracting in-component enter guards
const enterGuards = extractEnterGuards(activated, postEnterCbs, isValid)
const queue = enterGuards.concat(this.router.resolveHooks)
runQueue(queue, iterator, () => {
if (this.pending !== route) {
return abort()
}
this.pending = null
onComplete(route)
if (this.router.app) {
this.router.app.$nextTick(() => {
postEnterCbs.forEach(cb => { cb() })
})
}
})
})
复制代码
runQueue
方法的代码并不复杂,一个递归执行队列的方法,使用iterator(fn参数)
执行queue
,iterator
里给hook
传入的参数分别表明to
、from
、next
,在队列执行完后执行传入的回调方法。这里执行过程表明了vue-router的守卫函数的执行函数。
export function runQueue (queue: Array<?NavigationGuard>, fn: Function, cb: Function) {
const step = index => {
if (index >= queue.length) {
cb()
} else {
if (queue[index]) {
fn(queue[index], () => {
step(index + 1)
})
} else {
step(index + 1)
}
}
}
step(0)
}
复制代码
综上所述,路由跳转的变化大概上已经解释完,固然这并非完整的执行逻辑,只是路由跳转大概的过程差很少就是如此。