(解析)單頁應用路由實現沒那麼難--Vue

前言

單頁Web應用(single page web application,SPA),就是隻有一張Web頁面的應用,是加載單個HTML 頁面並在用戶與應用程序交互時動態更新該頁面的Web應用程序。簡單來講就是用戶只須要加載一次頁面就能夠再也不請求,當點擊其餘子頁面時只會有相應的URL改變而不會從新加載。
咱們能夠將實現路由的過程分爲兩部分:javascript

  1. 更新URL頁面不刷新
  2. 監聽URL的變化,執行頁面替換邏輯

如今主流有2種實現方案:html

  1. history.pushState等觸發popstate事件
  2. location.hash的變化觸發hashchange事件

接下來咱們一步一步看Vue-router如何實現的vue

Vue-router源碼解剖

構造器

請各位同窗翻到 src/index.js 第18行html5

export default class VueRouter {
  static install: () => void;
  static version: string;

  app: any;
  apps: Array<any>;
  ready: boolean;
  readyCbs: Array<Function>;
  options: RouterOptions;
  mode: string;
  history: HashHistory | HTML5History | AbstractHistory;
  matcher: Matcher;
  fallback: boolean;
  beforeHooks: Array<?NavigationGuard>;
  resolveHooks: Array<?NavigationGuard>;
  afterHooks: Array<?AfterNavigationHook>;

  constructor (options: RouterOptions = {}) { 
    this.app = null
    this.apps = []
    this.options = options
    this.beforeHooks = []
    this.resolveHooks = []
    this.afterHooks = []
    this.matcher = createMatcher(options.routes || [], this)

    let mode = options.mode || 'hash'
    this.fallback = mode === 'history' && !supportsPushState && options.fallback !== false
    if (this.fallback) {
      mode = 'hash'
    }
    if (!inBrowser) {
      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}`)
        }
    }
  }

構造器接收一個options參數java

clipboard.png

默認mode爲 "hash",若是顯示傳入參數mode爲"history",則進行 是否支持的"history"的判斷web

this.fallback = mode === 'history' && !supportsPushState && options.fallback !== false

supportsPushState方法 裏面 判斷了 是否爲瀏覽器環境且當前瀏覽器版本支持historyvue-router

clipboard.png

options.fallback用來控制路由,在設置了mode爲"history"可是當前瀏覽器環境不支持"history"的狀況下是否應該回調判斷,並從新設置mode爲"hash"。
設置fallback爲false本質上是爲了讓"router-link"在IE9上能夠完整的頁面刷新,若是是在hash模式下面不支持SSR,設置爲false,會讓那些在ie9服務端渲染的app更好用。(這段話沒具體應用過)數組

clipboard.png

以後根據不一樣的mode,來執行不一樣的方案瀏覽器

clipboard.png

HTML5History

clipboard.png

構造器

構造器接收2個參數,router和base
router是在定義新路由的時候建立的對象
base若是沒有傳,則爲undefined,
"?"這是經過一個名爲flow的外部工具爲javascript加上強類型檢查的功能,不影響編譯和運行。直接無視就好。
調動History的構造方法,History爲 HTML5History,HashHistory,AbstractHistory的超類app

clipboard.png

在History的構造方法中,

clipboard.png

若是base爲undefined,查找是否有base的元素,有就賦值,沒有就'/'
以後

const expectScroll = router.options.scrollBehavior
    const supportsScroll = supportsPushState && expectScroll

    if (supportsScroll) {
      setupScroll()
    }

判斷路由參數,是否控制路由頁面滾動條行爲

clipboard.png

監聽popstate事件,跳轉

clipboard.png

clipboard.png
獲取當前location的值以後,

clipboard.png
進行路由的更新,好比當前的History對應哪一個路由

clipboard.png

clipboard.png
Html5History也添加了go,push,replace等方法用來路由跳轉,

clipboard.png
先保存滾動條狀態,以後可使用history的自帶方法進行地址的改變
clipboard.png

更多詳情請見MDN

未完待續

HashHistory

構造器

clipboard.png
調動History的構造方法,History爲 HTML5History,HashHistory,AbstractHistory的超類
判斷當前hash地址

clipboard.png

若是開頭不是/#,將當前location按照hash格式化

clipboard.png

根據href獲取當前hash,若是沒有匹配到'#'返回空字符串。
初始化地址欄hash後

clipboard.png

clipboard.png

監聽popstate事件,替換路由,控制滾動條行爲

導航守衛

clipboard.png

在registerHook將設置的守衛入棧
在每次跳轉的時候,遞歸守衛集合,將觸發的守衛進行解析和執行。

clipboard.png

clipboard.png

AbstractHistory

構造器

clipboard.png
相對於上兩種方法,AbstractHistory看起來要簡單不少,這種模式是用於 Node.js 環境的,通常場景也就是在作測試的時候。可是在實際項目中其實還可使用的,利用這種特性仍是能夠很方便的作不少事情的。(我沒有用過)
由於不涉及和瀏覽器地址相關記錄關聯在一塊兒;總體流程依舊和 HashHistory 是同樣的,只是這裏經過數組來模擬瀏覽器歷史記錄堆棧信息。

clipboard.png

更新歷史堆棧信息,更新當前所處位置
等等,除了不使用瀏覽器的history對象,其餘的和html5history模式差很少。

小結

vue-router的源碼剖析到這裏就結束了,大概流程是這個樣子,得益於開發人員代碼的簡潔性及可讀性,咱們閱讀起來障礙仍是沒有那麼多,難度也沒有那麼大,總體邏輯不復雜,可是想要把不少不復雜的細節,整合到一塊兒,認真到細節,纔是程序設計的美學。

相關文章
相關標籤/搜索