淺析vue-router路由實現原理

前言

通過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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章