繼上一遍文章大概介紹了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)
}
複製代碼
綜上所述,路由跳轉的變化大概上已經解釋完,固然這並非完整的執行邏輯,只是路由跳轉大概的過程差很少就是如此。