Vue-Router源碼分析之index.js

前言

雖然最近需求着實很多,可是感受本身學習勁頭仍是蠻足的,並無被需求壓垮。今天,帶來Vue-Router源碼解析系列的第二篇文章:index.js。html

系列文章:前端

Vue-Router 源碼學習之咱們從API中看些門道vue

Vue-Router源碼分析之index.jshtml5

Vue-Router源碼分析之install方法node

正文

vue-router類裏面都作了什麼?

index.js是vue-router這個類的主構造函數,因此內容上算是比較關鍵的:面試

從圖片中咱們能夠看出來,這是一個ES6聲明類的方法,vue-router源碼中類的聲明都是使用類ES的語法, constructor (options: RouterOptions = {}),在vue-router中使用了flow.js作了類型的檢查,

什麼是flow.js?flow.js怎麼使用呢?由於篇幅緣由,這裏就暫時先不作涉及。各位小夥伴,能夠參看官網:flow.org/en/docs/typ…ajax

解析:constructor

首先咱們來看一下constructor內的代碼,vue-router

constructor (options: RouterOptions = {}) {
    this.app = null
    this.apps = []
    this.options = options
    this.beforeHooks = []
    this.resolveHooks = []
    this.afterHooks = []
    this.matcher = createMatcher(options.routes || [], this)
    //默認爲hash錨點
    let mode = options.mode || 'hash'
      //固然使用的是history模式 h5的pushState的方式來實現路由跳轉的,對options設置fallback屬性爲true時會回退到hash模式
      // 是否支持回退
    this.fallback = mode === 'history' && !supportsPushState && options.fallback !== false
    if (this.fallback) {
      mode = 'hash'
    }
    if (!inBrowser) {
      mode = 'abstract'
    }
    //沒有fallback的話選擇錨點模式,node環境選擇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}`)
        }
    }
  }
複製代碼

咱們聲明一個vue-router實例的時候是怎麼作的?編程

let router = new Router({
    base : '/',
    mode : 'history',
    routes : [{
        component : xxx,
        path : xxx
    },xxx]
})
複製代碼
constructor (options: RouterOptions = {}) 
options就是咱們剛纔上面的一個對象,裏面有base、mode、routes等屬性
複製代碼

這時候咱們知道options是個什麼東西了,咱們來看看內部對options進行了哪些處理。api

首先咱們說一下vue-router最核心的內容之一:

解析:mode

咱們知到vue-router的路由有兩種方式,一種是#錨點性的,第二種是和正常路徑同樣的,但是vue構建的應用是一個但頁面應用如何讓他像正常的多頁面應用同樣,是在不停的改變路徑呢? 這裏面就使用了html5的history的pushState與replaceState(讓頁面看起來無刷新的改變路徑),具體內容你們能夠看一下官網文檔和大神張鑫旭的博客(www.zhangxinxu.com/wordpress/2…

在vue-router源碼中有一個工具類專門作了這個事情:

咱們來看一下vue-router是如何匹配mode的吧:

// vue-router默認使用hash模式
let mode = options.mode || 'hash';
this.fallback = mode === 'history' && !supportsPushState && options.fallback !== false
// 若是選擇了history可是pushState方法並不能使用而且設置了
// 在當瀏覽器不支持 history.pushState 控制路由是否應該回退到 hash 模式的狀況下。
// (options.fallback默認就是true)
// 若是發現須要回退了,就回到hash錨點模式
    if (this.fallback) {
      mode = 'hash'
    }
// 不在瀏覽器環境就選擇abstract模式(在node環境)
    if (!inBrowser) {
      mode = 'abstract'
    }
    this.mode = mode
// 根據三種狀況是成不一樣的路由轉換實例。
// 若是沒有mode不是這三種狀況就報錯。
// HTML5History、HTML5History、HTML5History三個類都是繼承與一個base類
// 裏面有這三種模式對於路徑轉換時作的事情進行了必定的封裝。
    switch (mode) {
      case 'history':
        this.history = new HTML5History(this, options.base)
        break
      case 'hash':
        this.history = new HTML5History(this, options.base, this.fallback)
        break
      case 'abstract':
        this.history = new HTML5History(this, options.base)
        break
      default:
        if (process.env.NODE_ENV !== 'production') {
          assert(false, `invalid mode: ${mode}`)
        }
    }
複製代碼

到這裏你們應該對咱們寫的mode模式有一點的瞭解了吧。

解析:init

下面說一下init方法,上一章咱們講了在根節點的beforeCreate生命週期鉤子中,使用了init方法,若是忘記了能夠翻看上一篇install方法的學習來回歸一下

因此app就是根組件,init在執行前要判斷一下,vue-router是否是被vue成功use了,由於成功use以後,會把install方法的installed屬性設置爲true:

init (app: any /* Vue component instance */) {
    process.env.NODE_ENV !== 'production' && assert(
      install.installed,
      `not installed. Make sure to call \`Vue.use(VueRouter)\` ` +
      `before creating root instance.`
    )
    this.apps.push(app)
    // main app already initialized. (根組件已經被初始化)
    if (this.app) {
      return
    }

    this.app = app

    const history = this.history
    // 當咱們的根組件完成了對vue-router的init的時候咱們就要完成第一次路由的跳轉了
    // 當咱們的項目啓動的時候確定會有一個路徑,這個路徑是什麼不重要
    // 咱們在第一次進入這個路徑的時候,會進行vue-router的初始化
    // 初始化以後要開始展現對應的組件,但是咱們vue-router那些popState的事件確定沒有綁定,不會觸發啊,
    // 怎麼辦?? 那就手動觸發一下這個事情,
    // 第一次進入確定沒有對應的事件,不會完成跳轉時該作的事情。
    if (history instanceof HTML5History) {
      history.transitionTo(history.getCurrentLocation())
    } else if (history instanceof HashHistory) {
      const setupHashListener = () => {
        history.setupListeners()
      }
      history.transitionTo(
        history.getCurrentLocation(),
        setupHashListener,
        setupHashListener
      )
    }
    // 針對設置的不一樣mode(模式)
    // 將mode的時候,每種模式的history實例來源於三個不一樣的類,因此instanceof足夠判斷是哪一種模式。
複製代碼

到如今,咱們上一章的init函數已經串聯起來了,不知道你們感受怎麼樣?我感受舒服了不少了。

路由守衛

用過vue-router的同窗們都知道路由守衛的概念,這是在路由跳轉的先後等三個地方設置了不一樣的鉤子,幫助咱們在進入離開路由前作一些事情。這個鉤子是怎麼作的呢?

聲明瞭三個數組,存放每一個週期內的鉤子上綁定的函數。

vue-router全局級別的beforeEach、beforeResolve、afterEach作了什麼?

就這麼兩行代碼你敢信??我也很糾結你就幹了這麼點事情。 執行一下registerHook函數,從字面意思一看就是註冊鉤子,

怎麼註冊的呢?

function registerHook (list: Array<any>, fn: Function): Function {
  list.push(fn)
  // 返回值是一個function
  return () => {
    const i = list.indexOf(fn)
    if (i > -1) list.splice(i, 1)
  }
}
複製代碼

接收一個生命週期的鉤子數組,將咱們要執行的函數傳到數組內就能夠完成註冊了,我還沒看到這三個數組的內容,可是直覺告訴我頗有可能就是,觀察者模式(之後就探索去)。註冊到這應該就OK了,

爲何還有個返回值呢? 返回值的內容一看就是要清楚鉤子內的函數呀,咱們調用這個registerHook函數後,能夠獲得註冊函數的清除函數,清除的是鉤子數組中對應的函數,還有這麼一手,牛的一匹。(讓代碼教你如何熟練使用閉包~)

vue-router的編程式導航是怎麼作的?

push方法與replace、go方法調用對應路由轉換實例的對應方法,由於不一樣模式你們方法確定都不同, back與forward都是go方法傳入特殊參數,因此看到這裏咱們發現history這個實例的內容很關鍵。


清一清嗓

到了這裏咱們vue-router的主線流程咱們已經進行了一個梳理,不知道你們對這一塊內容感受滿意嗎? 不滿意就請不要郵寄刀片哈。

因此到如今咱們簡單進行一下總結

1:mode是設置模式的,有hash、history、abstract三種模式、每一個模式會致使vue-router實例的history不一樣,來自三個不一樣的類、每一個類又繼承於總的base類。 2:init方法會初始化整個組件、而且在vue-router的實例中設置了app屬性存放根組件(這個確實頗有用)、手動的完成初始化後的第一次路由跳轉。 3:beforeEach、beforeREsolve、afterEach三個全局的鉤子都有對應的鉤子函數數組,存放每一個週期鉤子內要作的事情、使用registerHook方法來完成鉤子函數的註冊,registerHook也能夠清除鉤子內對應的函數。 4:push、replace、go等方法都是使用history方法內的對應方法。

總結完畢 咱們學到了什麼? history真重要,我要好好看看他內部的實現

真的沒出息的總結。

vue-router類中還有一部分對options.routes的處理

options.routes 就是 [{path : 'xxx',components : 'xxx'}] 這個數組
複製代碼

生成一個根據options.routes的一份比對程序,完成程序的比對。

下一期的內容要在繼續學習index.js和開荒history中進行一個抉擇,具體是什麼內容你們能夠積極留言,給個方向呀。

結束語

每個前端er(boy and girl) 大家都不是一我的在戰鬥。

安排!!!!

我是一個應屆生,最近和朋友們維護了一個公衆號,內容是咱們在從應屆生過渡到開發這一路所踩過的坑,已經咱們一步步學習的記錄,若是感興趣的朋友能夠關注一下,一同加油~

我的公衆號:IT面試填坑小分隊
相關文章
相關標籤/搜索