使用
日常我們使用 vue-router 時:
在 template 中我們用 <router-link>
來做路由跳轉,用 <router-view>
來做路由跳轉後的展示。
<p>
<router-link to="/foo">Go to Foo</router-link>
<router-link to="/bar">Go to Bar</router-link>
</p>
<router-view></router-view>
複製程式碼
在 js 中,先用 Vue.use
來安裝 vue-router,然後 new 一個 VueRouter
例項,最後將例項注入到 Vue 例項中。
Vue.use(VueRouter)
const router = new VueRouter({
routes
})
const app = new Vue({
router
}).$mount('#app')
複製程式碼
這樣就實現了一個路由系統。我把這個過程稱為路由的安裝與初始化。
那麼這個過程裡,vue-router 做了什麼事情呢?接下來就來一探究竟。
對了,我選的是 vue-router v2.0.1 的程式碼來閱讀。
Flow
在閱讀 vue-router 的原始碼前,我們需要了解一下 Flow。
Flow 是 Facebook 的 JavaScript 靜態型別檢查工具,語法跟 TypeScript 有點類似。
原始碼裡就用了 Flow 來做靜態型別檢查。Vue-router 在 Flow 中的自定義型別存放在專案裡的 flow
目錄下。
想必你會問,為什麼不用 TypeScript 而是用 Flow 呢?這個作者的回答是最權威的,戳 這裡 瞭解一下吧。
猜想
在閱讀之前,我們先來簡單猜想一下,路由安裝與初始化會做哪些事情。
- 註冊兩個元件,
<router-link>
和<router-view>
- 通過
hash
或history
來實現前端路由 - 處理作為引數傳入的路由,匹配路由
- 將 VueRouter 物件例項注入 Vue 例項中
- ......
install
正式開始閱讀程式碼了,來驗證上面的猜想是否正確吧。
在 src/index.js
檔案中,有一段這樣的程式碼:
import { install } from './install'
// ...
VueRouter.install = install
if (inBrowser && window.Vue) {
window.Vue.use(VueRouter)
}
複製程式碼
我們知道呼叫了 Vue.use
就會預設去呼叫 install
方法,所以跳轉到 src/install.js
檔案中。
找到這段程式碼:
Vue.mixin({
beforeCreate () {
if (this.$options.router) {
this._router = this.$options.router
this._router.init(this)
Vue.util.defineReactive(this, '_route', this._router.history.current)
}
}
})
複製程式碼
這段程式碼,就是將 VueRouter 物件例項注入 Vue 例項中,賦值給屬性 _router
,同時創造了另一個屬性 _route
。
注意,這裡還會呼叫 init
方法,接下來會閱讀到。
下劃線表示私有屬性,如果需要給外部使用,則需要暴露一個方法或外部屬性出來:
Object.defineProperty(Vue.prototype, '$router', {
get () { return this.$root._router }
})
Object.defineProperty(Vue.prototype, '$route', {
get () { return this.$root._route }
})
複製程式碼
這樣子,就可以在 Vue 的元件裡 this.$router
或 this.$route
的呼叫。
最後註冊了兩個全域性元件,<router-link>
和 <router-view>
。
Vue.component('router-view', View)
Vue.component('router-link', Link)
複製程式碼
這裡有一個需要注意的點,Vue.use
會呼叫 install
方法,即以上的程式碼都會執行,但是 Vue 的 beforeCreate 鉤子是在 new Vue
的時候才會執行。
意思就是 new VueRouter
會在 beforeCreate
之前執行。即會先執行 VueRouter 的 constructor 建構函式。
constructor
來看看 VueRouter 的 constructor 建構函式做了哪些事情吧。constructor 的程式碼不多,主要是初始化一些屬性。
constructor (options: RouterOptions = {}) {
this.app = null
this.options = options
this.beforeHooks = []
this.afterHooks = []
this.match = createMatcher(options.routes || [])
let mode = options.mode || 'hash'
this.fallback = mode === 'history' && !supportsHistory
if (this.fallback) {
mode = 'hash'
}
if (!inBrowser) {
mode = 'abstract'
}
this.mode = mode
}
複製程式碼
裡面的 createMatcher
先跳過,這又是另一大塊,暫時不管。
可以看到後面的程式碼就是在設定 this.mode
,即路由模式。
預設是 hash
模式,如果設定了 history
還得判斷支援該種模式不,不支援則下降為預設的模式。如果程式碼不是執行在瀏覽器而是在 node 端,則設定為 abstract
模式,這個也先跳過哈哈哈。
在 beforeCreate
會呼叫 VueRouter 的 init
方法,來看看裡面做了什麼初始化工作吧。
init
init 方法是這麼被呼叫的:
this._router.init(this)
複製程式碼
這裡的 this
指向的是 Vue 例項。
再來看看 init 裡面的實現(過濾掉部分程式碼):
init (app: any /* Vue component instance */) {
this.app = app
const { mode, options, fallback } = this
switch (mode) {
case 'history':
this.history = new HTML5History(this, options.base)
break
case 'hash':
this.history = new HashHistory(this, options.base, fallback)
break
case 'abstract':
this.history = new AbstractHistory(this)
break
default:
assert(false, `invalid mode: ${mode}`)
}
this.history.listen(route => {
this.app._route = route
})
}
複製程式碼
init 實現的是,通過剛剛 constructor
設定的 mode
來生成一個新的屬性 this.history
。
this.history
是根據不同的 mode
來 new 出不同的物件例項。像 history 模式就用 HTML5History
,hash 模式就用 HashHistory
。
物件裡面的實現待以後再深入吧。現在我們只要知道有這麼一個新屬性 this.history
即可。
this.history
通過呼叫 listen 方法,將更新 _route
的操作儲存起來,在以後更新路由的時候,會執行該操作來更新 _route
。講了跟沒講一樣哈哈,沒關係,現在只要知道有這程式碼存在就行了。
回顧
VueRouter 的路由安裝與初始化做了哪些事情。按順序來:
(一)
呼叫 install
方法,註冊兩個元件,<router-link>
和 <router-view>
。
(二)
new 了 VueRouter 例項,呼叫 contructor 建構函式,初始化了一些屬性,其中包括 mode
屬性,用來儲存路由模式。
(三)
new 了 Vue 例項,呼叫其 beforeCreate
,將 VueRouter 物件例項注入 Vue 例項中,並呼叫 install
方法。install
方法則在根據不同的路由模式新增一個 history
屬性。history
屬性儲存的物件裡面又是一片天地,待續。