在解析原始碼前,先來了解下前端路由的實現原理。前端路由實現起來其實很簡單,本質就是監聽 URL 的變化,然後匹配路由規則,顯示相應的頁面,並且無須重新整理。目前單頁面使用的路由就只有兩種實現方式
- hash 模式
- history 模式
www.test.com/#/
就是 Hash URL,當 #
後面的雜湊值發生變化時,不會向伺服器請求資料,可以通過 hashchange
事件來監聽到 URL 的變化,從而進行跳轉頁面。
History 模式是 HTML5 新推出的功能,比之 Hash URL 更加美觀
VueRouter 原始碼解析
重要函式思維導圖
以下思維導圖羅列了原始碼中重要的一些函式
路由註冊
在開始之前,推薦大家 clone 一份原始碼對照著看。因為篇幅較長,函式間的跳轉也很多。
使用路由之前,需要呼叫 Vue.use(VueRouter)
,這是因為讓外掛可以使用 Vue
export function initUse (Vue: GlobalAPI) {
Vue.use = function (plugin: Function | Object) {
// 判斷重複安裝外掛 const installedPlugins = (this._installedPlugins || (this._installedPlugins = [])) if (installedPlugins.indexOf(plugin) >
-1) {
return this
} const args = toArray(arguments, 1) // 插入 Vue args.unshift(this) // 一般外掛都會有一個 install 函式 // 通過該函式讓外掛可以使用 Vue if (typeof plugin.install === 'function') {
plugin.install.apply(plugin, args)
} else if (typeof plugin === 'function') {
plugin.apply(null, args)
} installedPlugins.push(plugin) return this
}
}複製程式碼
接下來看下 install
函式的部分實現
export function install (Vue) {
// 確保 install 呼叫一次 if (install.installed &
&
_Vue === Vue) return install.installed = true // 把 Vue 賦值給全域性變數 _Vue = Vue const registerInstance = (vm, callVal) =>
{
let i = vm.$options._parentVnode if (isDef(i) &
&
isDef(i = i.data) &
&
isDef(i = i.registerRouteInstance)) {
i(vm, callVal)
}
} // 給每個元件的鉤子函式混入實現 // 可以發現在 `beforeCreate` 鉤子執行時 // 會初始化路由 Vue.mixin({
beforeCreate () {
// 判斷元件是否存在 router 物件,該物件只在根元件上有 if (isDef(this.$options.router)) {
// 根路由設定為自己 this._routerRoot = this this._router = this.$options.router // 初始化路由 this._router.init(this) // 很重要,為 _route 屬性實現雙向繫結 // 觸發元件渲染 Vue.util.defineReactive(this, '_route', this._router.history.current)
} else {
// 用於 router-view 層級判斷 this._routerRoot = (this.$parent &
&
this.$parent._routerRoot) || this
} registerInstance(this, this)
}, destroyed () {
registerInstance(this)
}
}) // 全域性註冊元件 router-link 和 router-view Vue.component('RouterView', View) Vue.component('RouterLink', Link)
}複製程式碼
對於路由註冊來說,核心就是呼叫 Vue.use(VueRouter)
,使得 VueRouter 可以使用 Vue。然後通過 Vue 來呼叫 VueRouter 的 install
函式。在該函式中,核心就是給元件混入鉤子函式和全域性註冊兩個路由元件。
VueRouter 例項化
在安裝外掛後,對 VueRouter 進行例項化。
const Home = {
template: '<
div>
home<
/div>
'
}const Foo = {
template: '<
div>
foo<
/div>
'
}const Bar = {
template: '<
div>
bar<
/div>
'
}// 3. Create the routerconst router = new VueRouter({
mode: 'hash', base: __dirname, routes: [ {
path: '/', component: Home
}, // all paths are defined without the hash. {
path: '/foo', component: Foo
}, {
path: '/bar', component: Bar
} ]
})複製程式碼
來看一下 VueRouter 的建構函式
constructor(options: RouterOptions = {
}) {
// ... // 路由匹配物件 this.matcher = createMatcher(options.routes || [], this) // 根據 mode 採取不同的路由方式 let mode = options.mode || 'hash' this.fallback = mode === 'history' &
&
!supportsPushState &
&
options.fallback !== false if (this.fallback) {
mode = 'hash'
} if (!inBrowser) {
mode = 'abstract'
} this.mode = mode switch (mode) {
case 'history': this.history = new HTML5History(this, options.base) break case 'hash': this.history = new HashHistory(this, options.base, this.fallback) break case 'abstract': this.history = new AbstractHistory(this, options.base) break default: if (process.env.NODE_ENV !== 'production') {
assert(false, `invalid mode: ${mode
}`)
}
}
}複製程式碼
在例項化 VueRouter 的過程中,核心是建立一個路由匹配物件,並且根據 mode 來採取不同的路由方式。
建立路由匹配物件
export function createMatcher ( routes: Array<
RouteConfig>
, router: VueRouter): Matcher {
// 建立路由對映表 const {
pathList, pathMap, nameMap
} = createRouteMap(routes) function addRoutes (routes) {
createRouteMap(routes, pathList, pathMap, nameMap)
} // 路由匹配 function match ( raw: RawLocation, currentRoute?: Route, redirectedFrom?: Location ): Route {
//...
} return {
match, addRoutes
}
}複製程式碼
createMatcher
函式的作用就是建立路由對映表,然後通過閉包的方式讓 addRoutes
和 match
函式能夠使用路由對映表的幾個物件,最後返回一個 Matcher
物件。
接下來看 createMatcher
函式時如何建立對映表的
export function createRouteMap ( routes: Array<
RouteConfig>
, oldPathList?: Array<
string>
, oldPathMap?: Dictionary<
RouteRecord>
, oldNameMap?: Dictionary<
RouteRecord>
): {
pathList: Array<
string>
;
pathMap: Dictionary<
RouteRecord>
;
nameMap: Dictionary<
RouteRecord>
;
} {
// 建立對映表 const pathList: Array<
string>
= oldPathList || [] const pathMap: Dictionary<
RouteRecord>
= oldPathMap || Object.create(null) const nameMap: Dictionary<
RouteRecord>
= oldNameMap || Object.create(null) // 遍歷路由配置,為每個配置新增路由記錄 routes.forEach(route =>
{
addRouteRecord(pathList, pathMap, nameMap, route)
}) // 確保萬用字元在最後 for (let i = 0, l = pathList.length;
i <
l;
i++) {
if (pathList[i] === '*') {
pathList.push(pathList.splice(i, 1)[0]) l-- i--
}
} return {
pathList, pathMap, nameMap
}
}// 新增路由記錄function addRouteRecord ( pathList: Array<
string>
, pathMap: Dictionary<
RouteRecord>
, nameMap: Dictionary<
RouteRecord>
, route: RouteConfig, parent?: RouteRecord, matchAs?: string) {
// 獲得路由配置下的屬性 const {
path, name
} = route const pathToRegexpOptions: PathToRegexpOptions = route.pathToRegexpOptions || {
} // 格式化 url,替換 / const normalizedPath = normalizePath( path, parent, pathToRegexpOptions.strict ) // 生成記錄物件 const record: RouteRecord = {
path: normalizedPath, regex: compileRouteRegex(normalizedPath, pathToRegexpOptions), components: route.components || {
default: route.component
}, instances: {
}, name, parent, matchAs, redirect: route.redirect, beforeEnter: route.beforeEnter, meta: route.meta || {
}, props: route.props == null ? {
} : route.components ? route.props : {
default: route.props
}
} if (route.children) {
// 遞迴路由配置的 children 屬性,新增路由記錄 route.children.forEach(child =>
{
const childMatchAs = matchAs ? cleanPath(`${matchAs
}/${child.path
}`) : undefined addRouteRecord(pathList, pathMap, nameMap, child, record, childMatchAs)
})
} // 如果路由有別名的話 // 給別名也新增路由記錄 if (route.alias !== undefined) {
const aliases = Array.isArray(route.alias) ? route.alias : [route.alias] aliases.forEach(alias =>
{
const aliasRoute = {
path: alias, children: route.children
} addRouteRecord( pathList, pathMap, nameMap, aliasRoute, parent, record.path || '/' // matchAs )
})
} // 更新對映表 if (!pathMap[record.path]) {
pathList.push(record.path) pathMap[record.path] = record
} // 命名路由新增記錄 if (name) {
if (!nameMap[name]) {
nameMap[name] = record
} else if (process.env.NODE_ENV !== 'production' &
&
!matchAs) {
warn( false, `Duplicate named routes definition: ` + `{
name: "${name
}", path: "${record.path
}"
}` )
}
}
}複製程式碼
以上就是建立路由匹配物件的全過程,通過使用者配置的路由規則來建立對應的路由對映表。
路由初始化
當根元件呼叫 beforeCreate
鉤子函式時,會執行以下程式碼
beforeCreate () {// 只有根元件有 router 屬性,所以根元件初始化時會初始化路由 if (isDef(this.$options.router)) {
this._routerRoot = this this._router = this.$options.router this._router.init(this) Vue.util.defineReactive(this, '_route', this._router.history.current)
} else {
this._routerRoot = (this.$parent &
&
this.$parent._routerRoot) || this
} registerInstance(this, this)
}複製程式碼
接下來看下路由初始化會做些什麼
init(app: any /* Vue component instance */) {
// 儲存元件例項 this.apps.push(app) // 如果根元件已經有了就返回 if (this.app) {
return
} this.app = app // 賦值路由模式 const history = this.history // 判斷路由模式,以雜湊模式為例 if (history instanceof HTML5History) {
history.transitionTo(history.getCurrentLocation())
} else if (history instanceof HashHistory) {
// 新增 hashchange 監聽 const setupHashListener = () =>
{
history.setupListeners()
} // 路由跳轉 history.transitionTo( history.getCurrentLocation(), setupHashListener, setupHashListener )
} // 該回撥會在 transitionTo 中呼叫 // 對元件的 _route 屬性進行賦值,觸發元件渲染 history.listen(route =>
{
this.apps.forEach(app =>
{
app._route = route
})
})
}複製程式碼
在路由初始化時,核心就是進行路由的跳轉,改變 URL 然後渲染對應的元件。接下來來看一下路由是如何進行跳轉的。
路由跳轉
transitionTo (location: RawLocation, onComplete?: Function, onAbort?: Function) {
// 獲取匹配的路由資訊 const route = this.router.match(location, this.current) // 確認切換路由 this.confirmTransition(route, () =>
{
// 以下為切換路由成功或失敗的回撥 // 更新路由資訊,對元件的 _route 屬性進行賦值,觸發元件渲染 // 呼叫 afterHooks 中的鉤子函式 this.updateRoute(route) // 新增 hashchange 監聽 onComplete &
&
onComplete(route) // 更新 URL this.ensureURL() // 只執行一次 ready 回撥 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)
})
}
})
}複製程式碼
在路由跳轉中,需要先獲取匹配的路由資訊,所以先來看下如何獲取匹配的路由資訊
function match ( raw: RawLocation, currentRoute?: Route, redirectedFrom?: Location): Route {
// 序列化 url // 比如對於該 url 來說 /abc?foo=bar&
baz=qux#hello // 會序列化路徑為 /abc // 雜湊為 #hello // 引數為 foo: 'bar', baz: 'qux' const location = normalizeLocation(raw, currentRoute, false, router) const {
name
} = location // 如果是命名路由,就判斷記錄中是否有該命名路由配置 if (name) {
const record = nameMap[name] // 沒找到表示沒有匹配的路由 if (!record) return _createRoute(null, location) const paramNames = record.regex.keys .filter(key =>
!key.optional) .map(key =>
key.name) // 引數處理 if (typeof location.params !== 'object') {
location.params = {
}
} if (currentRoute &
&
typeof currentRoute.params === 'object') {
for (const key in currentRoute.params) {
if (!(key in location.params) &
&
paramNames.indexOf(key) >
-1) {
location.params[key] = currentRoute.params[key]
}
}
} if (record) {
location.path = fillParams(record.path, location.params, `named route "${name
}"`) return _createRoute(record, location, redirectedFrom)
}
} else if (location.path) {
// 非命名路由處理 location.params = {
} for (let i = 0;
i <
pathList.length;
i++) {
// 查詢記錄 const path = pathList[i] const record = pathMap[path] // 如果匹配路由,則建立路由 if (matchRoute(record.regex, location.path, location.params)) {
return _createRoute(record, location, redirectedFrom)
}
}
} // 沒有匹配的路由 return _createRoute(null, location)
}複製程式碼
接下來看看如何建立路由
// 根據條件建立不同的路由function _createRoute( record: ?RouteRecord, location: Location, redirectedFrom?: Location): Route {
if (record &
&
record.redirect) {
return redirect(record, redirectedFrom || location)
} if (record &
&
record.matchAs) {
return alias(record, location, record.matchAs)
} return createRoute(record, location, redirectedFrom, router)
}export function createRoute ( record: ?RouteRecord, location: Location, redirectedFrom?: ?Location, router?: VueRouter): Route {
const stringifyQuery = router &
&
router.options.stringifyQuery // 克隆引數 let query: any = location.query || {
} try {
query = clone(query)
} catch (e) {
} // 建立路由物件 const route: Route = {
name: location.name || (record &
&
record.name), meta: (record &
&
record.meta) || {
}, path: location.path || '/', hash: location.hash || '', query, params: location.params || {
}, fullPath: getFullPath(location, stringifyQuery), matched: record ? formatMatch(record) : []
} if (redirectedFrom) {
route.redirectedFrom = getFullPath(redirectedFrom, stringifyQuery)
} // 讓路由物件不可修改 return Object.freeze(route)
}// 獲得包含當前路由的所有巢狀路徑片段的路由記錄// 包含從根路由到當前路由的匹配記錄,從上至下function formatMatch(record: ?RouteRecord): Array<
RouteRecord>
{
const res = [] while (record) {
res.unshift(record) record = record.parent
} return res
}複製程式碼
至此匹配路由已經完成,我們回到 transitionTo
函式中,接下來執行 confirmTransition
transitionTo (location: RawLocation, onComplete?: Function, onAbort?: Function) {
// 確認切換路由 this.confirmTransition(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) &
&
route.matched.length === current.matched.length ) {
this.ensureURL() return abort()
} // 通過對比路由解析出可複用的元件,需要渲染的元件,失活的元件 const {
updated, deactivated, activated
} = resolveQueue( this.current.matched, route.matched ) 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)
}
} // 導航守衛陣列 const queue: Array<
?NavigationGuard>
= [].concat( // 失活的元件鉤子 extractLeaveGuards(deactivated), // 全域性 beforeEach 鉤子 this.router.beforeHooks, // 在當前路由改變,但是該元件被複用時呼叫 extractUpdateHooks(updated), // 需要渲染元件 enter 守衛鉤子 activated.map(m =>
m.beforeEnter), // 解析非同步路由元件 resolveAsyncComponents(activated) ) // 儲存路由 this.pending = route // 迭代器,用於執行 queue 中的導航守衛鉤子 const iterator = (hook: NavigationGuard, next) =>
{
// 路由不相等就不跳轉路由 if (this.pending !== route) {
return abort()
} try {
// 執行鉤子 hook(route, current, (to: any) =>
{
// 只有執行了鉤子函式中的 next,才會繼續執行下一個鉤子函式 // 否則會暫停跳轉 // 以下邏輯是在判斷 next() 中的傳參 if (to === false || isError(to)) {
// next(false) this.ensureURL(true) abort(to)
} else if ( typeof to === 'string' || (typeof to === 'object' &
&
(typeof to.path === 'string' || typeof to.name === 'string')) ) {
// next('/') 或者 next({
path: '/'
}) ->
重定向 abort() if (typeof to === 'object' &
&
to.replace) {
this.replace(to)
} else {
this.push(to)
}
} else {
// 這裡執行 next // 也就是執行下面函式 runQueue 中的 step(index + 1) next(to)
}
})
} catch (e) {
abort(e)
}
} // 經典的同步執行非同步函式 runQueue(queue, iterator, () =>
{
const postEnterCbs = [] const isValid = () =>
this.current === route // 當所有非同步元件載入完成後,會執行這裡的回撥,也就是 runQueue 中的 cb() // 接下來執行 需要渲染元件的導航守衛鉤子 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()
})
})
}
})
})
}export function runQueue (queue: Array<
?NavigationGuard>
, fn: Function, cb: Function) {
const step = index =>
{
// 佇列中的函式都執行完畢,就執行回撥函式 if (index >
= queue.length) {
cb()
} else {
if (queue[index]) {
// 執行迭代器,使用者在鉤子函式中執行 next() 回撥 // 回撥中判斷傳參,沒有問題就執行 next(),也就是 fn 函式中的第二個引數 fn(queue[index], () =>
{
step(index + 1)
})
} else {
step(index + 1)
}
}
} // 取出佇列中第一個鉤子函式 step(0)
}複製程式碼
接下來介紹導航守衛
const queue: Array<
?NavigationGuard>
= [].concat( // 失活的元件鉤子 extractLeaveGuards(deactivated), // 全域性 beforeEach 鉤子 this.router.beforeHooks, // 在當前路由改變,但是該元件被複用時呼叫 extractUpdateHooks(updated), // 需要渲染元件 enter 守衛鉤子 activated.map(m =>
m.beforeEnter), // 解析非同步路由元件 resolveAsyncComponents(activated))複製程式碼
第一步是先執行失活元件的鉤子函式
function extractLeaveGuards(deactivated: Array<
RouteRecord>
): Array<
?Function>
{// 傳入需要執行的鉤子函式名 return extractGuards(deactivated, 'beforeRouteLeave', bindGuard, true)
}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)
}
}) // 陣列降維,並且判斷是否需要翻轉陣列 // 因為某些鉤子函式需要從子執行到父 return flatten(reverse ? guards.reverse() : guards)
}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 ))
}))
}複製程式碼
第二步執行全域性 beforeEach 鉤子函式
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)
}
}複製程式碼
在 VueRouter 類中有以上程式碼,每當給 VueRouter 例項新增 beforeEach 函式時就會將函式 push 進 beforeHooks 中。
第三步執行 beforeRouteUpdate
鉤子函式,呼叫方式和第一步相同,只是傳入的函式名不同,在該函式中可以訪問到 this
物件。
第四步執行 beforeEnter
鉤子函式,該函式是路由獨享的鉤子函式。
第五步是解析非同步元件。
export function resolveAsyncComponents (matched: Array<
RouteRecord>
): Function {
return (to, from, next) =>
{
let hasAsync = false let pending = 0 let error = null // 該函式作用之前已經介紹過了 flatMapComponents(matched, (def, _, match, key) =>
{
// 判斷是否是非同步元件 if (typeof def === 'function' &
&
def.cid === undefined) {
hasAsync = true pending++ // 成功回撥 // once 函式確保非同步元件只載入一次 const resolve = once(resolvedDef =>
{
if (isESModule(resolvedDef)) {
resolvedDef = resolvedDef.default
} // 判斷是否是建構函式 // 不是的話通過 Vue 來生成元件建構函式 def.resolved = typeof resolvedDef === 'function' ? resolvedDef : _Vue.extend(resolvedDef) // 賦值元件 // 如果元件全部解析完畢,繼續下一步 match.components[key] = resolvedDef pending-- if (pending <
= 0) {
next()
}
}) // 失敗回撥 const reject = once(reason =>
{
const msg = `Failed to resolve async component ${key
}: ${reason
}` process.env.NODE_ENV !== 'production' &
&
warn(false, msg) if (!error) {
error = isError(reason) ? reason : new Error(msg) next(error)
}
}) let res try {
// 執行非同步元件函式 res = def(resolve, reject)
} catch (e) {
reject(e)
} if (res) {
// 下載完成執行回撥 if (typeof res.then === 'function') {
res.then(resolve, reject)
} else {
const comp = res.component if (comp &
&
typeof comp.then === 'function') {
comp.then(resolve, reject)
}
}
}
}
}) // 不是非同步元件直接下一步 if (!hasAsync) next()
}
}複製程式碼
以上就是第一個 runQueue
中的邏輯,第五步完成後會執行第一個 runQueue
中回撥函式
// 該回撥用於儲存 `beforeRouteEnter` 鉤子中的回撥函式const postEnterCbs = []const isValid = () =>
this.current === route// beforeRouteEnter 導航守衛鉤子const enterGuards = extractEnterGuards(activated, postEnterCbs, isValid)// beforeResolve 導航守衛鉤子const queue = enterGuards.concat(this.router.resolveHooks)runQueue(queue, iterator, () =>
{
if (this.pending !== route) {
return abort()
} this.pending = null // 這裡會執行 afterEach 導航守衛鉤子 onComplete(route) if (this.router.app) {
this.router.app.$nextTick(() =>
{
postEnterCbs.forEach(cb =>
{
cb()
})
})
}
})複製程式碼
第六步是執行 beforeRouteEnter
導航守衛鉤子,beforeRouteEnter
鉤子不能訪問 this
物件,因為鉤子在導航確認前被呼叫,需要渲染的元件還沒被建立。但是該鉤子函式是唯一一個支援在回撥中獲取 this
物件的函式,回撥會在路由確認執行。
beforeRouteEnter (to, from, next) {
next(vm =>
{
// 通過 `vm` 訪問元件例項
})
}複製程式碼
下面來看看是如何支援在回撥中拿到 this
物件的
function extractEnterGuards( activated: Array<
RouteRecord>
, cbs: Array<
Function>
, isValid: () =>
boolean): Array<
?Function>
{// 這裡和之前呼叫導航守衛基本一致 return extractGuards( activated, 'beforeRouteEnter', (guard, _, match, key) =>
{
return bindEnterGuard(guard, match, key, cbs, isValid)
} )
}function bindEnterGuard( guard: NavigationGuard, match: RouteRecord, key: string, cbs: Array<
Function>
, isValid: () =>
boolean): NavigationGuard {
return function routeEnterGuard(to, from, next) {
return guard(to, from, cb =>
{
// 判斷 cb 是否是函式 // 是的話就 push 進 postEnterCbs next(cb) if (typeof cb === 'function') {
cbs.push(() =>
{
// 迴圈直到拿到元件例項 poll(cb, match.instances, key, isValid)
})
}
})
}
}// 該函式是為了解決 issus #750// 當 router-view 外面包裹了 mode 為 out-in 的 transition 元件 // 會在元件初次導航到時獲得不到元件例項物件function poll( cb: any, // somehow flow cannot infer this is a function instances: Object, key: string, isValid: () =>
boolean) {
if ( instances[key] &
&
!instances[key]._isBeingDestroyed // do not reuse being destroyed instance ) {
cb(instances[key])
} else if (isValid()) {
// setTimeout 16ms 作用和 nextTick 基本相同 setTimeout(() =>
{
poll(cb, instances, key, isValid)
}, 16)
}
}複製程式碼
第七步是執行 beforeResolve
導航守衛鉤子,如果註冊了全域性 beforeResolve
鉤子就會在這裡執行。
第八步就是導航確認,呼叫 afterEach
導航守衛鉤子了。
以上都執行完成後,會觸發元件的渲染
history.listen(route =>
{
this.apps.forEach(app =>
{
app._route = route
})
})複製程式碼
以上回撥會在 updateRoute
中呼叫
updateRoute(route: Route) {
const prev = this.current this.current = route this.cb &
&
this.cb(route) this.router.afterHooks.forEach(hook =>
{
hook &
&
hook(route, prev)
})
}複製程式碼
至此,路由跳轉已經全部分析完畢。核心就是判斷需要跳轉的路由是否存在於記錄中,然後執行各種導航守衛函式,最後完成 URL 的改變和元件的渲染。
求職
最近本人在尋找工作機會,如果有杭州的不錯崗位的話,歡迎聯絡我 zx597813039@gmail.com。
公眾號
最後
如果你有不清楚的地方或者認為我有寫錯的地方,歡迎評論區交流。
相關文章