Vue-Router路由模式和install過程

  單頁應用(SPA, Single Page Application)的整個Web系統由一個html文件,經過Ajax和後端進行數據交互,經過一些特殊手段去加載渲染頁面的不一樣部分,使得無需刷新總體頁面,,就像使用app同樣,極大的提高了用戶使用體驗,在Vue生態中,就是利用Vue的核心插件-Vue-Router來實現Web界面的路由跳轉,so,本文就是經過學習Vue-Router,瞭解Vue-Router完成SPA開發的實現具體原理。javascript

Web路由簡介

  先簡單說一下路由,早期Web系統路由主要是服務器後端實現,熟悉先後端開發的朋友就知道,在服務器端,經過路由表映射到相應的方法,並執行相應的動做,好比咱們想要請求API數據、新頁面、文件等:html

"/" -> goHtml()

"/api/users" -> queryUsers()

  客戶端實現路由主要就有兩種方式:前端

1. 基於Web Url的hash
2. 基於Hishtory API

基於Web Url的hash

   Url中的hash值是「#」號後面的參數值,好比http://www.example.com/index.html#location1裏面的值localtion1, 主要有以下一些特性(以前看到公司項目的Url中帶有「#」,雖然有點莫名,但都沒去想一想咋來的,汗):vue

1. Url中#後的內容不會發送到服務器;
2. hash值的改變會保存到瀏覽器的歷史瀏覽記錄中,可經過瀏覽器的回退按鈕查看;
3. hash值是可讀寫的,可經過window.location.hash獲取到hash值,利用onhashchange監聽hash值的變化(Vue-Router裏的hash模式就是在onhashchange中的回調事件中完成路由到界面的更新渲染)。

基於Hishtory API

  基於hash模式雖然方便,但帶有#號,有同窗就會覺這樣的Url有點醜啦,爲了和同向服務器後端請求的Url風格保持一致,就可使用Hishtory API模式了。使用基於Hishtory API主要是操做HTML5提供的操做瀏覽器歷史記錄API,能夠實現地址無刷新更新地址欄,例如:java

// pushState  state: 用於描述新記錄的一些特性。
// 這個參數會被一併添加到歷史記錄中,以供之後使用。
// 這個參數是開發者根據本身的須要自由給出的。
// pushState做用至關於修改「#」後面的值
window.history.pushState(state, "title", "/userPage");
// 好比瀏覽器後退
window.addEventListener("popstate", function(e) {
    var state = e.state;
    // do something...
});
// 與pushState相對僅進行Url替換而不產生新的瀏覽記錄的replaceState方法
window.history.replaceState(null, null, "/userPage");

兩種模式對比

  咱們來簡單對比一下這兩種模式的一些特色: node

|模式 VS | hash | Hishtory API |
| ------ | ------ | ------ |
| 美觀程度 | 公認略醜,帶#號 | 相對美觀 |
| 兼容性 | 好 | 須要瀏覽器支持HTML5 |
| 錯誤URL後端支持 | 否 | 是 | git

  因此兩種模式的使用要根據實際項目的需求來定了,接下來是重點啦,Vue-Router內部實現路由就是基於這兩種模式!因此提早了解一下前端路由的兩種模式算是打個預防針。github

Vue-Router

   先回憶一下Vue-Router的使用的四步曲,:vue-router

import Vue from 'vue';
import VueRouter from 'vue-router'
// 1. 使用Vue-Router插件
Vue.use(VueRouter)
// 2. VueRouter實例化
const router = new VueRouter({
  model: 'history', // 路由模式 hash\history
  routes: [
    {
      name: 'xx',
      path: 'path',
      component: component
    }
  ]
});
// 3. 實例化Vue實例時使用該router路由
new Vue({
  ...
  router,
});

// 4. 經過Vue.$router.push('xx') 或 router-link進行路由跳轉

  接下來,就逐次窺探上面四步曲中Vue-Router具體的一些實現細節。後端

使用Vue-Router插件

  Vue-Router遵循Vue插件的開發規範,經過調用Vue內部方法Vue.use()對VueRouter進行install(其實是回調VueRouter中所定義的install方法),這一過程完成了VueRouter的掛載工做。

-> vue\src\core\global-api\use.js
Vue.use = function (plugin: Function | Object) {
  ... 
  plugin.install.apply(plugin, args);
  ...
}

  回到VueRouter源碼中,分析一下install過程的執行流程:

import View from './components/view'
import Link from './components/link'

export let _Vue

// install實現
export function install (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({
    // 路由鉤子函數,在vue執行beforeCreate鉤子函數回調時會一塊兒調用,由於beforeCreate是一個數組
    beforeCreate () {
      Vue.$options = options
      if (isDef(this.$options.router)) {
        this._routerRoot = this
        this._router = this.$options.router
        // 調用init
        this._router.init(this)
        // 調用Vue內部util方法使得當前的url實現響應式化
        Vue.util.defineReactive(this, '_route', this._router.history.current)
      } else {
        this._routerRoot = (this.$parent && this.$parent._routerRoot) || this
      }
      // 註冊實例
      registerInstance(this, this)
    },
    destroyed () {
      registerInstance(this)
    }
  })
  // 將$router和$route掛載到Vue的原型鏈上
  Object.defineProperty(Vue.prototype, '$router', {
    get () { return this._routerRoot._router }
  })

  Object.defineProperty(Vue.prototype, '$route', {
    get () { return this._routerRoot._route }
  })

  // 註冊路由視圖和路由連接兩個組件
  Vue.component('RouterView', View)
  Vue.component('RouterLink', Link)

  // 執行Vue的選項合併策略
  const strats = Vue.config.optionMergeStrategies
  // use the same hook merging strategy for route hooks
  strats.beforeRouteEnter = strats.beforeRouteLeave = strats.beforeRouteUpdate = strats.created
}

  能夠看出,在install內部主要作了三個事:

    1. 就是利用Vue的mixin方法混入了全局beforeCreate和destoryed,在首次加載執行beforeCreate時,指定_routerRoot爲當前的Vue實例,而且執行Vue-Router的init初始化工做(install最核心仍是init初始化,這個具體細節稍後進行介紹),在beforeCreate方法內,主要是對父組件(也就是router所掛載的節點)進行初始化操做,並設置當前的_routerRoot爲該組件對應的實例,在該逐漸中去初始化init路由的一些配置(好比設置路由模式等),其餘組件好比(router-link, router-view會將_routerRoot設置爲在options參數中包含了router選項的父(祖先)組件),並執行registerInstance方法,該方法中的registerRouteInstance目前實際上是router-view組件中所定義的一個方法,主要做用是執行當前組件的渲染工做,以下:
const registerInstance = (vm, callVal) => {
    // 父虛擬節點
    let i = vm.$options._parentVnode
    if (isDef(i) && isDef(i = i.data) && isDef(i = i.registerRouteInstance)) {
      i(vm, callVal)
    }
  }

  這個混入至關因而定義,具體執行時等各組件在進行實例化時按照生命週期回調beforeCreate和destoryed這兩個鉤子函數,destroyed做用是在進行好比路由切換後、實際上做用就是清除上一個展現在router-view中的組件所渲染的內容。

    1. 將$router和$route掛載到Vue的原型鏈上,能夠經過this.$router和this.$route進行使用;
    1. 註冊router-link和router-view兩個組件到Vue中。

總結

  因爲但願篇幅不要太大,否則看起來比較吃力,因此本篇文章先就寫到這裏,關於實例化和具體使用所涉及到的原理工做後面文章再討論。在這對本篇文章作一個小結:

  • 簡要介紹了前端路由,以及實現前端路由的兩種模式 URL hash和history API並對這二者作了對比;
  • 介紹了Vue是如何use Vue-Router組件,實際上內部就是回調Vue-Router內部定義的install方法;
  • 介紹了install方法的一些主要工做職能:

    • 混入beforeCreate和destoryed方法;
    • 全局掛載$router和$route;
    • 註冊router-link和router-view兩個組件。

另外,歡迎去本人git 可相互學習和star。

相關文章
相關標籤/搜索