vue-router 原始碼:路由的安裝與初始化

cobish發表於2018-07-27

使用

日常我們使用 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 呢?這個作者的回答是最權威的,戳 這裡 瞭解一下吧。

猜想

在閱讀之前,我們先來簡單猜想一下,路由安裝與初始化會做哪些事情。

  1. 註冊兩個元件,<router-link><router-view>
  2. 通過 hashhistory 來實現前端路由
  3. 處理作為引數傳入的路由,匹配路由
  4. 將 VueRouter 物件例項注入 Vue 例項中
  5. ......

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.$routerthis.$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 屬性儲存的物件裡面又是一片天地,待續。

相關文章