Vue-router 原理

原文:https://www.cnblogs.com/xuzhudong/p/8869699.html
前端應用的主流形式:單頁應用(SPA) 大型的單頁應用最顯著的特色之一:採用前端路由系統,經過改變URL,在不從新請求頁面的狀況下,更新頁面視圖html

更新視圖但不從新請求頁面是前端路由原理的核心之一,目前唉瀏覽器環境中這一功能的實現主要有兩種方式:前端

  • 利用URL中的hash("#")
  • 利用History interface在html5中新增的方法

h5 history api:https://developer.mozilla.org/zh-CN/docs/Web/API/Historyvue

vue-router是Vue.js框架的路由插件,下面咱們從它的源碼入手,瞭解下vue-router 是如何經過這兩種方式實現前端路由的html5

模式參數

在vue-router中是經過mode這一參數控制路由的實現模式的:vue-router

const router = new VueRouter({
    mode: 'history',
    routes: [...]
})

複製代碼

vue-router的實際源碼:api

export default class VueRouter {
 
 mode: string; // 傳入的字符串參數,指示history類別
 history: HashHistory | HTML5History | AbstractHistory; // 實際起做用的對象屬性,必須是以上三個類的枚舉
 fallback: boolean; // 如瀏覽器不支持,'history'模式需回滾爲'hash'模式
 
 constructor (options: RouterOptions = {}) {
 
 let mode = options.mode || 'hash' // 默認爲'hash'模式
 this.fallback = mode === 'history' && !supportsPushState // 經過supportsPushState判斷瀏覽器是否支持'history'模式
 if (this.fallback) {
     mode = 'hash'
 }
 if (!inBrowser) {
     mode = 'abstract' // 不在瀏覽器環境下運行需強制爲'abstract'模式
 }
 this.mode = mode

 // 根據mode肯定history實際的類並實例化
 switch (mode) {
 case 'history':
     this.history = new HTML5History(this, options.base); breakcase 'hash':
      this.history = new HashHistory(this, options.base, this.fallback);breakcase 'abstract':
      this.history = new AbstractHistory(this, options.base); break
 default:
      if (process.env.NODE_ENV !== 'production') {
           assert(false, `invalid mode: $`)
      }
    }
 }

 init (app: any /* Vue component instance */) {
 const history = this.history;
 // 根據history的類別執行相應的初始化操做和監聽
 if (history instanceof HTML5History) {
      history.transitionTo(history.getCurrentLocation())
 } else if (history instanceof HashHistory) {
     const setupHashListener = () => {
     history.setupListeners()
 }
 history.transitionTo(
     history.getCurrentLocation(),
     setupHashListener,
     setupHashListener
   )
 }

 history.listen(route => {
     this.apps.forEach((app) => {
        app._route = route
   })
 })
 }

 // VueRouter類暴露的如下方法實際是調用具體history對象的方法
VueRouter.prototype.beforeEach = function beforeEach (fn) {
     return registerHook(this.beforeHooks, fn)
};

VueRouter.prototype.beforeResolve = function beforeResolve (fn) {
    return registerHook(this.resolveHooks, fn)
};

VueRouter.prototype.afterEach = function afterEach (fn) {
    return registerHook(this.afterHooks, fn)
};

VueRouter.prototype.onReady = function onReady (cb, errorCb) {
    this.history.onReady(cb, errorCb);
};

VueRouter.prototype.onError = function onError (errorCb) {
    this.history.onError(errorCb);
};

VueRouter.prototype.push = function push (location, onComplete, onAbort) {
    this.history.push(location, onComplete, onAbort);
};

VueRouter.prototype.replace = function replace (location, onComplete, onAbort) {
    this.history.replace(location, onComplete, onAbort);
};

VueRouter.prototype.go = function go (n) {
   this.history.go(n);
};

VueRouter.prototype.back = function back () {
   this.go(-1);
};

VueRouter.prototype.forward = function forward () {
    this.go(1);
};

複製代碼

從上面代碼能夠看出:瀏覽器

  1. 做爲參數創圖的字符串屬性mode只是一個標記,用來只是實際起做用的對象屬性history的的實現類,二者對應關係以下: mode的對應--history:HTML5History; 'hash':HashHishtory; abstract:AbstractHistory
  2. 在初始化對應的history以前,會對mode作一些校驗:若瀏覽器不支持HTML5History方式(經過supportPushState變量判斷),則mode強制設爲'abstract'
  3. VueRouter類中的onReady(), push()等方法只是一個代理,實際是調用的具體history對象的對應方法,在init()方法中初始化時,也是根據history對象具體的類別執行不一樣操做

在瀏覽器環境下的兩種方式,分別就是在HTML5History,HashHistory兩個雷中實現的。History中定義的是公用和基礎的方法,簡單說下: ### HashHistory hash("#")符號的原本做用是加在URL中指示網頁中的位置:
> http://www.example.com/index.html#print
複製代碼
  • #號自己以及它後面的字符稱之爲hash,可經過window.location.hash屬性讀取。它具備如下特色:bash

    • hash雖然出如今URL中,但不會被包括在HTTP請求中。它是用來指導瀏覽器動做的,對服務器端徹底無用,所以,改變hash不會從新加載頁面
    • 能夠爲hash的改變添加監聽事件

    window.addEventListener("hashchange", funcRef, false)服務器

    • 每一次改變hash(window.location.hash),都會在瀏覽器的訪問歷史中增長一個記錄

    因而可知,利用hash的特色,就尅來實現前端路由「更新視圖但不從新請求頁面」的功能。 app

    HashHistory.push()

    源碼:

    HashHistory.prototype.push = function push (location, onComplete, onAbort) {
    var this$1 = this;
    
    var ref = this;
    var fromRoute = ref.current;
    this.transitionTo(location, function (route) {
      pushHash(route.fullPath);
      handleScroll(this$1.router, route, fromRoute, false);
      onComplete && onComplete(route);
    }, onAbort);
    };
    // 對window的hash進行直接賦值
    function pushHash (path) {
    if (supportsPushState) {
        pushState(getUrl(path));
    } else {
        window.location.hash = path;
    }
    }
    
    複製代碼

transitionTo()方法是父類中定義用來處理路由變化中的基礎邏輯的,push()方法最主要的是對window的hash進行了直接賦值

pushHash(route.fullPath)

hash的改變會自動添加到瀏覽器的訪問歷史記錄中


經過Vue.mixin()方法,全局註冊一個混合,影響註冊以後全部建立的每一個Vue實例,該混合在beforeCreate鉤子洪經過Vue.util.defineReactive()定義了響應式的_route屬性。所謂響應式屬性,即當_route值改變時,會自動調用Vue實例中的render()方法,更新視圖。
總結一下,從**設置路由**改變到**視圖更新**的流程以下:

$router.push() -->HashHistory.push() --> History.transitionTo() --> History.updateRoute() --> vm.render()