前言
通過B站視頻和一些童鞋的文章結合GitHub源碼閱讀來理解路由的實現原理
看過前章vuex狀態管理的分享之後,相信對路由這塊也是非常感興趣的,同樣的模式,同樣的方式,我們走進GitHub之vue-router
同樣直接走進 src
- components:route-link 組件 和 router-view 組件 實現
- history:關於瀏覽器相關,包含 hash模式 , basic模式 ,html5模式 以及非瀏覽器模式以及go 、push 、replace 、back 等各自的處理方法
- util:不用多說,各種工具方法
- create-mathcher: 這個就比較重要的了,創建路由的匹配和添加路由配置項的方法
- create-mathcher-map:跟創建路由匹配直接相關,創建路由映射map表,並封裝route對象
- index: 入口文件,也是vueRouter構造函數實體,原型上定義了 go 、push 、replace 、back 等
- install:初始化
老樣子,Vue.use(“vue-router”) 會直接執行 install
初始化進行安裝插件,這裏我就不多做解釋了,不太理解的童鞋可以去前章看一下簡單的介紹。
src/index.js 代碼最後片段
VueRouter.install = install
VueRouter.version = '__VERSION__'
if (inBrowser && window.Vue) {
window.Vue.use(VueRouter) // 進行初始化和安裝路由插件
}
src/install.js
代碼不多,簡單看一下
import View from './components/view' // 導入router-view
import Link from './components/link' // 導入router-link
export let _Vue
export function install (Vue) { // vue 是根實例
if (install.installed && _Vue === Vue) return
install.installed = true
_Vue = Vue
const isDef = v => v !== undefined
const registerInstance = (vm, callVal) => {
let i = vm.$options._parentVnode
if (isDef(i) && isDef(i = i.data) && isDef(i = i.registerRouteInstance)) {
i(vm, callVal)
}
}
Vue.mixin({ // 類似 vuex 通過 vue.mixin 在生命週期創建前,將 router 掛載在vue根實例上
beforeCreate () {
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) // 註冊實例
},
destroyed () {
registerInstance(this) // 銷燬實例
}
})
Object.defineProperty(Vue.prototype, '$router', { // 掛載到原型上
get () { return this._routerRoot._router }
})
// 這裏通過 Object.defineProperty 定義 get 來實現
//而不使用 Vue.prototype.$router = this.this._routerRoot._router。是爲了讓其只讀,不可修改
Object.defineProperty(Vue.prototype, '$route', { // 掛載到原型上
get () { return this._routerRoot._route }
})
Vue.component('RouterView', View) // 註冊全局組件
Vue.component('RouterLink', Link) // 註冊全局組件
const strats = Vue.config.optionMergeStrategies
// use the same hook merging strategy for route hooks
strats.beforeRouteEnter = strats.beforeRouteLeave = strats.beforeRouteUpdate = strats.created
}
好了,install
初始化寫的很清楚,跟 vuex
非常相似,都是安裝,註冊相關組件,通過mixin
在生命週期創建前將實例掛載在vue
根實例上。
走進核心src/index.js vueRouter 構造函數
代碼量較大,分塊解析
根據mode確定模式
// constructor 裏 通過 mode 來判斷時哪一種模式, 默認 hash
let mode = options.mode || 'hash'
// 是否支持 history 模式 this.fallback
this.fallback = mode === 'history' && !supportsPushState && options.fallback !== false
if (this.fallback) { // 不支持則默認使用 hash
mode = 'hash'
}
if (!inBrowser) { // 非瀏覽器操作,對應目錄history裏邊的非瀏覽器模式
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}`)
}
}
通過常用路由跳轉方式push來分析整個流程
src/index.js vueRouter 構造函數 push
一目瞭然,可以看出來在這裏基本什麼都沒有操作,只是做了一個轉發,給到history
模塊了
push (location: RawLocation, onComplete?: Function, onAbort?: Function) {
// $flow-disable-line
if (!onComplete && !onAbort && typeof Promise !== 'undefined') {
return new Promise((resolve, reject) => {
this.history.push(location, resolve, reject)
})
} else {
this.history.push(location, onComplete, onAbort)
}
}
hash.js 之 push
// 50 行
push (location: RawLocation, onComplete?: Function, onAbort?: Function) {
const { current: fromRoute } = this
this.transitionTo(
location,
route => {
pushHash(route.fullPath)
handleScroll(this.router, route, fromRoute, false)
onComplete && onComplete(route)
},
onAbort
)
}
// 切換路由 141行 對應方法 pushHash
function pushHash(path) {
if (supportsPushState) {
pushState(getUrl(path));
} else {
window.location.hash = path;
}
}
html5.js 之 push
// 44 行
push (location: RawLocation, onComplete?: Function, onAbort?: Function) {
const { current: fromRoute } = this
this.transitionTo(location, route => {
pushState(cleanPath(this.base + route.fullPath))
handleScroll(this.router, route, fromRoute, false)
onComplete && onComplete(route)
}, onAbort)
}
//切換路由 8行,pushState 對應方法在工具方法裏邊
import { pushState, replaceState, supportsPushState } from '../util/push-state'
列舉兩種模式炸眼一看,大同小異呀,都是調用 this.transitionTo
方法,唯一的區別就是一個調用 pushHash , 一個調用 pushState. 不同的處理方式 。
註釋:其他的 go 、 replace 、ensureURL 、getCurrentLocation 等都是同樣的實現方式
好,路由核心的實現方式已經大概瞭解了,那麼路由的匹配規則在這裏簡單介紹一下,我也沒有繼續深入了哈,畢竟只是一個匹配規則,我們走進 createMatcher
創建匹配器方法
src/create-matcher.js 16 行
export function createMatcher(
routes: Array<RouteConfig>, // route 對象
router: VueRouter // 當前路由實例 this
): Matcher {
// 創建路由映射 createRouteMap 重點,重點,重點方法
const { pathList, pathMap, nameMap } = createRouteMap(routes);
// 匹配規則
function match(...){...}
/*
匹配規則方法,根據路徑來匹配路由,並創建路由對象 route , 也就是我們後來使用到的 this.$route
然後就是 create-matcher-map 創建映射關係表了,類似 {"/home":Home} 等於 { path:component}
根據path hash路徑來映射對應的組件
*/
當然有興趣的童鞋可以通過珠峯公開課來進一步瞭解具體的實現方式,賦地址
珠峯公開課vuex+vue-router