vue-router 原理

1渣叛、前端路由

在 Web 前端單頁應用 SPA(Single Page Application)中,路由描述的是 URL 與 UI 之間的映射關
系,這種映射是單向的特幔,即 URL 變化引起 UI 更新(無需刷新頁面)。

vue-router 是 Vue.js 官方的路由插件闸昨,它和 vue.js 是深度集成的薄风,適合用于構建單頁面應用。

那與傳統(tǒng)的頁面跳轉有什么區(qū)別呢撇他?

  • vue 的單頁面應用是基于路由和組件的逆粹,路由用于設定訪問路徑僻弹,并將路徑和組件映射起來蹋绽。
  • 傳統(tǒng)的頁面應用,是用一些超鏈接來實現(xiàn)頁面切換和跳轉的蚣抗。

在 vue-router 單頁面應用中翰铡,則是路徑之間的切換,也就是組件的切換迷捧。路由模塊的本質(zhì)就是建立起 url 和頁面之間的映射關系漠秋。

2庆锦、包含的功能

Vue Router 包含的功能有:

  • 嵌套的路由/視圖表
  • 模塊化的肥荔、基于組件的路由配置
  • 路由參數(shù)中符、查詢淀散、通配符
  • 基于 Vue.js 過渡系統(tǒng)的視圖過渡效果
  • 細粒度的導航控制
  • 帶有自動激活的 CSS class 的鏈接
  • HTML5 歷史模式或 hash 模式,在 IE9 中自動降級
  • 自定義的滾動條行為

3郭膛、vue-router 實現(xiàn)原理

在 vue-router 中,可以通過三種方式來實現(xiàn)前端路由的變化如捅,分別為 hash己肮、history 和 abstract谎僻。

3.1 hash

hash 是 URL 中 hash (#) 及后面的那部分戈稿,常用作錨點在頁面內(nèi)進行導航,最重要的是改變 URL 中的 hash 部分不會引起頁面刷新跳昼。

我們可以通過 hashchange 事件監(jiān)聽 URL 的變化鹅颊,改變 URL 的方式只有這幾種:

  • 通過瀏覽器前進后退改變 URL
  • 通過<a>標簽改變 URL
  • 通過window.location改變 URL

我們通過下面的例子锚烦,來看下 hash 實現(xiàn)路由的原理:

<!DOCTYPE html>
<html lang="en">
  <body>
    <ul>
      <ul>
        <!-- 定義路由 -->
        <li><a href="#/home">home</a></li>
        <li><a href="#/about">about</a></li>

        <!-- 渲染路由對應的 UI -->
        <div id="routeView"></div>
      </ul>
    </ul>
  </body>
  <script>
    let routerView = document.getElementById('routeView');
    window.addEventListener('hashchange', () => {
      routerView.innerHTML = location.hash;
    });
    window.addEventListener('DOMContentLoaded', () => {
      if (!location.hash) {
        //如果不存在hash值,那么重定向到#/
        location.hash = '/';
      } else {
        //如果存在hash值彻亲,那就渲染對應UI
        routerView.innerHTML = location.hash;
      }
    });
  </script>
</html>

上面的代碼很簡單,只要注意一下幾點:

  • 我們通過 a 標簽的 href 屬性來改變 URL 的 hash 值宙址。當然,觸發(fā)瀏覽器的前進后退按鈕也可以舀患,或者在控制臺輸入 window.location 賦值來改變 hash 都是可以的聊浅。也都會觸發(fā) hashchange。
  • 我們監(jiān)聽 hashchange 事件。一旦事件觸發(fā)售碳,就改變 routerView 的內(nèi)容间景,若是在 vue 中,這改變的應當是 router-view 這個組件的內(nèi)容封拧。
  • 為何又監(jiān)聽了 load 事件?這時因為頁面第一次加載完不會觸發(fā) hashchange曹铃,因而用 load 事件來監(jiān)聽 hash 值铛只,再將視圖渲染成對應的內(nèi)容淳玩。

3.2 history

由于 html5 標準的發(fā)布,history 的 api 增加了兩個 API非竿。pushState 和 replaceState蜕着。通過這兩個 API 可以改變 url 地址且不會發(fā)送請求。同時還有 popstate 事件红柱。通過這些就能用另一種方式來實現(xiàn)前端路由了承匣,但原理都是跟 hash 實現(xiàn)相同的。

用了 HTML5 的實現(xiàn)锤悄,單頁路由的 url 就不會多出一個#韧骗,變得更加美觀隶症。但因為沒有 # 號胁住,所以當用戶刷新頁面之類的操作時,瀏覽器還是會給服務器發(fā)送請求笋婿。為了避免出現(xiàn)這種情況,所以這個實現(xiàn)需要服務器的支持,需要把所有路由都重定向到根頁面嗅虏。

我們主要說幾個注意點:

  • 通過 pushState/replaceState 或<a>標簽改變 URL 不會觸發(fā)頁面刷新,也不會觸發(fā) popstate 方法谆刨。所以我們可以攔截 pushState/replaceState 的調(diào)用和<a>標簽的點擊事件來檢測 URL 變化她我,從而觸發(fā) router-view 的視圖更新。
  • 通過瀏覽器前進后退改變 URL 芽偏,或者通過 js 調(diào)用 history 的 back捂齐,go,forward 方法贵少,都會觸發(fā) popstate 事件录平,所以我們可以監(jiān)聽 popstate 來觸發(fā) router-view 的視圖更新绪氛。

所以涝影,我們其實是需要監(jiān)聽 popstate 以及攔截 pushState/placeState 以及 a 的點擊去實現(xiàn)監(jiān)聽 URL 的變化握童。

我們通過下面的例子,來看下 history 實現(xiàn)路由的原理:

<!DOCTYPE html>
<html lang="en">
  <body>
    <ul>
      <ul>
        <li><a href="/home">home</a></li>
        <li><a href="/about">about</a></li>

        <div id="routeView"></div>
      </ul>
    </ul>
  </body>
  <script>
    let routerView = document.getElementById('routeView');
    window.addEventListener('DOMContentLoaded', () => {
      routerView.innerHTML = location.pathname;
      var linkList = document.querySelectorAll('a[href]');
      linkList.forEach(el =>
        el.addEventListener('click', function(e) {
          e.preventDefault();
          history.pushState(null, '', el.getAttribute('href'));
          routerView.innerHTML = location.pathname;
        }),
      );
    });
    window.addEventListener('popstate', () => {
      routerView.innerHTML = location.pathname;
    });
  </script>
</html>

注意一下幾個重點:

  • 我們監(jiān)聽 popState 事件定拟。一旦事件觸發(fā)(例如觸發(fā)瀏覽器的前進后端按鈕于微,或者在控制臺輸入 history逗嫡,go,back株依,forward 賦值)驱证,就改變 routerView 的內(nèi)容。
  • 我們通過 a 標簽的 href 屬性來改變 URL 的 path 值恋腕。這里需要注意的就是抹锄,當改變 path 值時,默認會觸發(fā)頁面的跳轉荠藤,所以需要攔截 <a> 標簽點擊事件的默認行為伙单,這樣就阻止了 a 標簽自動跳轉的行為, 點擊時使用 pushState 修改 URL 并更新手動 UI哈肖,從而實現(xiàn)點擊鏈接更新 URL 和 UI 的效果吻育。

3.3 abstract

abstract 是 vue 路由中的第三種模式,支持所有 JavaScript 運行環(huán)境淤井,如 Node.js 服務器端布疼。如果發(fā)現(xiàn)沒有瀏覽器的 API,路由會自動強制進入這個模式币狠。

4游两、使用 vue-router

首先,我們當然是通過 vue-cli 去創(chuàng)建一個腳手架总寻,這一步比較簡單器罐,就直接省略了。然后渐行,我們修改

App.vue:

<template>
  <div id="app">
    <div id="nav">
      <router-link to="/home">Home</router-link> |
      <router-link to="/about">About</router-link>
    </div>
    <router-view />
  </div>
</template>

router/index.js

import Vue from 'vue';
import VueRouter from 'vue-router';
import Home from '../views/Home.vue';
import About from '../views/About.vue';
Vue.use(VueRouter);
const routes = [
  {
    path: '/home',
    name: 'Home',
    component: Home,
  },
  {
    path: '/about',
    name: 'About',
    component: About,
  },
];
const router = new VueRouter({
  mode: 'history',
  routes,
});
export default router;

至于 home 組件和 about 組件轰坊,就是非常簡單的一句話,就不放出來了祟印。

到這里最基本的使用 vue-router 就完成了肴沫。

再將 VueRouter 引入改成我們的 myVueRouter.js

import VueRouter from './myVueRouter'; //修改代碼

5、剖析 VueRouter 本質(zhì)

我們在仔細看看 vue 是如何引入 vue-router 的蕴忆。

  1. 首先颤芬,我們通過 import VueRouter from 'vue-router' 引入 vue-router。
  2. 然后套鹅,通過 Vue.use(VueRouter) 去觸發(fā)了 vue-router 中的 install 方法站蝠。
  3. 最后,const router = new VueRouter({...}), 再把 router 作為參數(shù)的一個屬性值卓鹿,new Vue({router})复局。

我們看看這三步氯葬,特別是最后一步我們是 new 出來的一個 vue-router 的實例喜最,所以,我們可以預見聚蝶,VueRouter 應該是一個類。所以我們這樣寫:

class VueRouter {}

然后通過 Vue.use() 我們知道藻治,VueRouter 應該有個 install 方法碘勉,并且第一個參數(shù)應該是 Vue 實例。

let Vue = null;
class VueRouter {}
VueRouter.install = v => {
  Vue = v;
};
// 然后導出
export default VueRouter;

5.1 加載組件

vue-router 還自帶了兩個組件桩卵,分別是 router-link 和 router-view

(1)router-link

組件支持用戶在具有路由功能的應用中 (點擊) 導航验靡。 通過 to 屬性指定目標地址,默認渲染成帶有正確鏈接的 <a> 標簽

使用 router-link 理由如下:

  • 無論是 HTML5 history 模式還是 hash 模式吸占,它的表現(xiàn)行為一致晴叨,所以,當你要切換路由模式矾屯,或者在 IE9 降級使用 hash 模式,無須作任何變動初厚。
  • 在 HTML5 history 模式下件蚕,router-link 會守衛(wèi)點擊事件,讓瀏覽器不再重新加載頁面产禾。
  • 當你在 HTML5 history 模式下使用 base 選項之后排作,所有的 to 屬性都不需要寫 (基路徑) 了。

(2)router-view

組件是一個 functional 組件亚情,渲染路徑匹配到的視圖組件妄痪。還可以內(nèi)嵌自己,根據(jù)嵌套路徑楞件,渲染嵌套組件衫生。

(3)加載 router-link 和 router-view:

let Vue = null;
class VueRouter {}
VueRouter.install = v => {
  Vue = v;
  // 新增代碼
  Vue.component('router-link', {
    render(h) {
      return h('a', {}, 'home');
    },
  });
  Vue.component('router-view', {
    render(h) {
      return h('div', {}, 'home視圖');
    },
  });
};
export default VueRouter;

這個時候,我們的腳手架應該可以跑起來了土浸,并且顯示 home 視圖罪针。

5.2 加載 $router

Vue.install 中除了加載 router-link 和 router-view 這兩個組件以外,還有很重要的一個功能黄伊。那就是加載 $router$route泪酱。

$route$router 有什么區(qū)別?

$router 是 VueRouter 的實例對象还最,$route 是當前路由對象墓阀,也就是說 $route$router 的一個屬性。

其實 $router 中有一個 history 屬性拓轻,它代表了 hashHistory 還是 HTML5history 實例對象斯撮,然后這個對象里面有個 current 屬性,這個屬性對象就是 $route悦即。

通過 main.js 中的 new Vue({router})吮成,我們可以把 router 實例(也就是剛剛 new 出來的)掛載到根組件的 $options 中橱乱。可是我們發(fā)現(xiàn)粱甫,我們在每個實例組件泳叠,都可以通過 this.$router 訪問到 router 實例。而這個茶宵,就是在 install 中實現(xiàn)的危纫。

// myVueRouter.js
let Vue = null;
class VueRouter {}

VueRouter.install = function(v) {
  Vue = v;
  // 新增代碼
  Vue.mixin({
    beforeCreate() {
      if (this.$options && this.$options.router) {
        // 如果是根組件
        this._root = this; //把當前實例掛載到_root上
        this._router = this.$options.router;
      } else {
        //如果是子組件
        this._root = this.$parent && this.$parent._root;
      }
      // 定義 $router
      Object.defineProperty(this, '$router', {
        get() {
          return this._root._router;
        },
      });
    },
  });

  Vue.component('router-link', {
    render(h) {
      return h('a', {}, '首頁');
    },
  });
  Vue.component('router-view', {
    render(h) {
      return h('h1', {}, '首頁視圖');
    },
  });
};

解釋一下上面的代碼:

  1. 首先,mixin 的作用是將 mixin 的內(nèi)容混合到 Vue 的初始參數(shù) options 中乌庶。
  2. 為什么是 beforeCreate 而不是 created 呢种蝶?因為如果是在 created 操作的話,$options 已經(jīng)初始化好了瞒大。
  3. 在 beforeCreate 中螃征,我們判斷如果是根組件,我們將傳入的實例和 router 分別綁定到 _root 和 _router 上透敌。
  4. 如果是子組件盯滚,我們就去遞歸讀取到根組件,綁定到 _root 上酗电。
  5. 我們?yōu)?vue 的原型對象魄藕,定義 $router,然后返回值是 _root(根組件)的 _router撵术。

那么背率,我們還有一個問題,為什么判斷當前組件是子組件嫩与,就可以直接從父組件拿到 _root 根組件呢寝姿?

這就需要我們了解父子組件的渲染順尋了,其實很簡單蕴纳,直接列出來了:

父 beforeCreate -> 父 created -> 父 beforeMounte ->
子 beforeCreate -> 子 create -> 子 beforeMount ->子 mounted ->
父 mounted

可以看到会油,在執(zhí)行子組件的 beforeCreate 的時候,父組件已經(jīng)執(zhí)行完 beforeCreate 了古毛,那理所當然父組件已經(jīng)有 _root 了翻翩。

5.3 完善構造器

首先,我們看看稻薇,我們在 new VueRouter 的時候嫂冻,傳入了什么參數(shù):

const router = new VueRouter({
  mode: 'history',
  routes,
});

可以看到,暫時傳入了兩個塞椎,一個是 mode桨仿,還有一個是 routes 數(shù)組。因此案狠,我們可以這樣實現(xiàn)構造器服傍。

class VueRouter {
  constructor(options) {
    this.mode = options.mode || 'hash';
    this.routes = options.routes || []; // 你傳遞的這個路由是一個數(shù)組表
  }
}

由于直接處理數(shù)組比較不方便钱雷,所以我們做一次轉換,采用 path 為 key吹零,component 為 value 的方式罩抗。

class VueRouter {
  constructor(options) {
    this.mode = options.mode || 'hash';
    this.routes = options.routes || [];
    // 新增代碼
    this.routesMap = this.changeMap(this.routes);
  }
  // 新增代碼
  createMap(routes) {
    return routes.reduce((pre, current) => {
      pre[current.path] = current.component;
      return pre;
    }, {});
  }
}

接下來,我們還需要在 vue-router 的實例中保存當前路徑(在包含一些例如 params 信息灿椅,其實就是 $route)套蒂,所以我們?yōu)榱朔奖愎芾恚褂靡粋€對象來表示:

class HistoryRoute {
  constructor() {
    this.current = null;
  }
}
class VueRouter {
  constructor(options) {
    this.mode = options.mode || 'hash';
    this.routes = options.routes || [];
    this.routesMap = this.changeMap(this.routes);
    // 新增代碼
    this.history = new HistoryRoute();
  }
  createMap(routes) {
    return routes.reduce((pre, current) => {
      pre[current.path] = current.component;
      return pre;
    }, {});
  }
}

這個時候茫蛹,我們的 history 對象中操刀,就保存了關于當前路徑的屬性 current。

只是婴洼,我們這個時候的 current 還是 null骨坑,所以,我們需要做初始化操作柬采。

在初始化的時候卡啰,我們需要判斷當前是什么模式,然后將當前路徑保存到 current 中警没。

//myVueRouter.js
let Vue = null;
class HistoryRoute {
  constructor() {
    this.current = null;
  }
}
class VueRouter {
  constructor(options) {
    this.mode = options.mode || 'hash';
    this.routes = options.routes || [];
    this.routesMap = this.createMap(this.routes);
    this.history = new HistoryRoute();
    // 新增代碼;
    this.init();
  }
  // 新增代碼;
  init() {
    if (this.mode === 'hash') {
      // 先判斷用戶打開時有沒有 hash 值,沒有的話跳轉到 #/
      location.hash ? '' : (location.hash = '/');
      window.addEventListener('load', () => {
        this.history.current = location.hash.slice(1);
      });
      window.addEventListener('hashchange', () => {
        this.history.current = location.hash.slice(1);
      });
    } else {
      location.pathname ? '' : (location.pathname = '/');
      window.addEventListener('load', () => {
        this.history.current = location.pathname;
      });
      window.addEventListener('popstate', () => {
        this.history.current = location.pathname;
      });
    }
  }

  createMap(routes) {
    return routes.reduce((pre, current) => {
      pre[current.path] = current.component;
      return pre;
    }, {});
  }
}

5.4 完善 $route

現(xiàn)在振湾,我們已經(jīng)在構造器中根據(jù)不同模式杀迹,將路徑賦值給 history 的 current,那么押搪,我們現(xiàn)在就可以給 $route 賦值了树酪,當然 this.$route 也就是 current。

VueRouter.install = function(v) {
  Vue = v;
  Vue.mixin({
    beforeCreate() {
      if (this.$options && this.$options.router) {
        // 如果是根組件
        this._root = this; //把當前實例掛載到_root上
        this._router = this.$options.router;
      } else {
        //如果是子組件
        this._root = this.$parent && this.$parent._root;
      }
      Object.defineProperty(this, '$router', {
        get() {
          return this._root._router;
        },
      });
      // 新增代碼;
      Object.defineProperty(this, '$route', {
        get() {
          return this._root._router.history.current;
        },
      });
    },
  });
  Vue.component('router-link', {
    render(h) {
      return h('a', {}, '首頁');
    },
  });
  Vue.component('router-view', {
    render(h) {
      return h('h1', {}, '首頁視圖');
    },
  });
};

代碼極其簡單大州,就是在 vue 的原型鏈上再次定義了一個 $route续语,指向 current 即可。

5.5 完善 router-view

我們已經(jīng)通過 current 保存到了當前的路徑厦画,那么我們現(xiàn)在就可以根據(jù)當前的路徑疮茄,拿到對應的 component,然后根暑,將這個 component 渲染出來就好了力试。

Vue.component('router-view', {
  render(h) {
    // 新增代碼
    let current = this._self._root._router.history.current;
    let routeMap = this._self._root._router.routesMap;
    return h(routeMap[current]);
  },
});

但是這個時候,我們頁面是渲染不出來的排嫌,原因在于畸裳,current 并不是響應式的,我們拿到的 current 是最初的淳地,也就是 null怖糊。所以帅容,我們需要把 current 變成響應式,只要路徑變化伍伤,我們的 router-view 就必須跟著變化并徘。

我們需要用到 Vue 中的一個工具方法,這個方法很簡單嚷缭,就是把變量變成響應式饮亏。那么在什么時候去把 current 變成響應式呢,其實很多地方都可以阅爽,例如在第一次定義 history 的時候路幸,或者在根組件渲染出來 $router 的時候,我們就在第一次定義 history 的時候去改變付翁。

constructor(options) {
 this.mode = options.mode || 'hash';
 this.routes = options.routes || [];
 this.routesMap = this.changeMap(this.routes);
 // this.history = new HistoryRoute();
 // 新增代碼
 // 將history變成響應式的數(shù)據(jù)
 Vue.util.defineReactive(this,"history",new HistoryRoute());
 this.init();
 };

5.6 完善 router-link

首先简肴,我們應該比較清楚 router-link 的使用。

<router-link to="/home">Home</router-link>
<router-link to="/about">About</router-link>

很顯然百侧,父組件間 to 這個路徑傳進去砰识,子組件接收。所以我們實現(xiàn)代碼如下:

Vue.component('router-link', {
  props: {
    to: String,
  },
  render(h) {
    // 新增代碼
    let mode = this._self._root._router.mode;
    let to = mode === 'hash' ? '#' + this.to : this.to;
    return h('a', { attrs: { href: to } }, this.$slots.default);
  },
});

這個時候佣渴,我們把 router-link 渲染成了 a 標簽辫狼,到這里,你們腳手架應該點擊 router-link 就能切換相應的組件了辛润。

Vue.mixin({
  beforeCreate() {
    if (this.$options && this.$options.router) {
      // 如果是根組件
      this._root = this; //把當前實例掛載到_root上
      this._router = this.$options.router;
      // 新增代碼
      // 攔截 router-link
      this._router.mode === 'history' &&
        document.addEventListener('click', e => {
          if (e.target.className === 'router-link-to') {
            // 阻止默認跳轉事件;
            e.preventDefault();
            // 手動改變url路徑
            history.pushState(null, '', e.target.getAttribute('href'));
            // 為current賦值url路徑
            this._router.history.current = location.pathname;
          }
        });
    } else {
      //如果是子組件
      this._root = this.$parent && this.$parent._root;
    }
  },
});

上面代碼攔截比較簡單膨处,解釋一下:

  1. 判斷如果是 router-link 的 a 標簽,并且是 history 模式砂竖,那么就阻止默認跳轉事件真椿。
  2. 通過 history.pushState 方法去手動的改變 url 的路徑,這個方法改變 url 不會刷新頁面乎澄,這個很重要突硝。
  3. 當然這樣改變 url 也不會觸發(fā) popstate 方法,所以我們手動給 current 賦值置济。
  4. 因為我們的 history 已經(jīng)是動態(tài)響應的了解恰,所以很自然,這個時候 router-view 里面的組件也就更新了舟肉。

到此修噪,我們對于 vue-router 的實現(xiàn)基本完成了,附上全部的 vue-router 的代碼:

//myVueRouter.js
let Vue = null;

class HistoryRoute {
  constructor() {
    this.current = null;
  }
}

class VueRouter {
  constructor(options) {
    this.mode = options.mode || "hash";
    this.routes = options.routes || [];
    this.routesMap = this.createMap(this.routes);
    // this.history = new HistoryRoute();
    Vue.util.defineReactive(this, "history", new HistoryRoute());
    this.init();
  }
  createMap(routes) {
    return routes.reduce((pre, current) => {
      pre[current.path] = current.component;
      return pre;
    }, {});
  }
  init() {
    if (this.mode === "hash") {
      // 先判斷用戶打開時有沒有 hash 值路媚,沒有的話跳轉到 #/
      location.hash ? "" : (location.hash = "/");
      window.addEventListener("load", () => {
        this.history.current = location.hash.slice(1);
      });
      window.addEventListener("hashchange", () => {
        this.history.current = location.hash.slice(1);
      });
    } else {
      location.pathname ? "" : (location.pathname = "/");
      window.addEventListener("load", () => {
        this.history.current = location.pathname;
      });
      window.addEventListener("popstate", () => {
        this.history.current = location.pathname;
      });
    }
  }
}

VueRouter.install = (v) => {
  Vue = v;
  Vue.mixin({
    beforeCreate() {
      if (this.$options && this.$options.router) {
        this._root = this;
        this._router = this.$options.router;
        this._router.mode === "history" &&
          document.addEventListener("click", (e) => {
            if (e.target.className === "router-link-to") {
              // 阻止默認跳轉事件;
              e.preventDefault();
              // 手動改變url路徑
              history.pushState(null, "", e.target.getAttribute("href"));
              // 為current賦值url路徑
              this._router.history.current = location.pathname;
            }
          });
      } else {
        this._root = this.$parent && this.$parent._root;
      }
      Object.defineProperty(this, "$router", {
        get() {
          return this._root._router;
        },
      });

      Object.defineProperty(this, "$route", {
        get() {
          return this._root._router.history.current;
        },
      });
    },
  });
  Vue.component("router-link", {
    props: {
      to: String,
    },
    render(h) {
      let mode = this._self._root._router.mode;
      let to = mode === "hash" ? "#" + this.to : this.to;
      return h(
        "a",
        { attrs: { href: to }, class: "router-link-to" },
        this.$slots.default
      );
    },
  });
  Vue.component("router-view", {
    render(h) {
      let current = this._self._root._router.history.current;
      let routeMap = this._self._root._router.routesMap;
      return h(routeMap[current]);
    },
  });
};

export default VueRouter;

6黄琼、總結

  1. 在 main.js 中導入,使用 Vue.use() 方法注冊組件,然后就會調(diào)用 install() 方法脏款,并構造一個 VueRouter 實例導出围苫。

  2. 在 install() 方法中:

  • 注冊了兩個子組件:router-link 和 router-view;
  • 調(diào)用 Vue.mixin() 方法混入 beforeCreate() 方法撤师,然后判斷是否為根組件剂府,如果是則掛載 this 到 this._root 上并且掛 _router,否則將根組件掛載到子組件的 _root 上剃盾;
  • 調(diào)用 Object.defineProperty 將 $router 和 $route 綁定到 Vue.prototype 上腺占,$router = this._root._router$route = this._root._router.history.current痒谴。
  1. 在 VueRouter 實例中:
  • 進入 constructor(options) 實例化并傳入 options 參數(shù)衰伯,得到 this.mode 和 this.routes,再將 this.routes 數(shù)組進行 map 化處理积蔚;
  • 然后調(diào)用 Vue.util.defineReactive 將 this.history 變成響應式意鲸,其值為 HistoryRoute 的實例,實例中定義了一個 this.current 變量尽爆;
  • 再然后調(diào)用 init() 方法進行初始化怎顾,判斷 this.mode 等于 hash 則監(jiān)聽 load 和 hashChange 事件,得到 location.hash 并賦值給 current漱贱,否則判斷 this.mode 等于 history 則監(jiān)聽 load 和 popstate 事件槐雾,得到 loaction.pathname 并賦值給 current。

7幅狮、參考

https://juejin.cn/post/6854573222231605256

https://juejin.cn/post/6844903665388486664

https://juejin.cn/post/6844903961745440775

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蚜退,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子彪笼,更是在濱河造成了極大的恐慌,老刑警劉巖蚂且,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件配猫,死亡現(xiàn)場離奇詭異,居然都是意外死亡杏死,警方通過查閱死者的電腦和手機泵肄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來淑翼,“玉大人腐巢,你說我怎么就攤上這事⌒ǎ” “怎么了冯丙?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長遭京。 經(jīng)常有香客問我胃惜,道長泞莉,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任船殉,我火速辦了婚禮鲫趁,結果婚禮上,老公的妹妹穿的比我還像新娘利虫。我一直安慰自己挨厚,他們只是感情好,可當我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布糠惫。 她就那樣靜靜地躺著疫剃,像睡著了一般。 火紅的嫁衣襯著肌膚如雪寞钥。 梳的紋絲不亂的頭發(fā)上慌申,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天,我揣著相機與錄音理郑,去河邊找鬼蹄溉。 笑死,一個胖子當著我的面吹牛您炉,可吹牛的內(nèi)容都是我干的柒爵。 我是一名探鬼主播,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼赚爵,長吁一口氣:“原來是場噩夢啊……” “哼棉胀!你這毒婦竟也來了?” 一聲冷哼從身側響起冀膝,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤唁奢,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后窝剖,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體麻掸,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年赐纱,在試婚紗的時候發(fā)現(xiàn)自己被綠了脊奋。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡疙描,死狀恐怖诚隙,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情起胰,我是刑警寧澤久又,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響籽孙,放射性物質(zhì)發(fā)生泄漏烈评。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一犯建、第九天 我趴在偏房一處隱蔽的房頂上張望讲冠。 院中可真熱鬧,春花似錦适瓦、人聲如沸竿开。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽否彩。三九已至,卻和暖如春嗦随,著一層夾襖步出監(jiān)牢的瞬間列荔,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工枚尼, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留贴浙,地道東北人署恍。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓崎溃,卻偏偏與公主長得像,于是被迫代替她去往敵國和親盯质。 傳聞我的和親對象是個殘疾皇子袁串,可洞房花燭夜當晚...
    茶點故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內(nèi)容