Vue Router的手寫實現

爲何須要前端路由

在先後端分離的如今,大部分應用的展現方式都變成了 SPA(單頁面應用 Single Page Application)的模式。爲何會選擇 SPA 呢?緣由在於:html

  • 用戶的全部操做都在同一個頁面下進行,不進行頁面的跳轉。用戶體驗好。
  • 對比多頁面,單頁面不須要屢次向服務器請求加載頁面(只請求一次.html文件),只須要向服務器請求數據(多虧了 ajax)。所以,瀏覽器不須要渲染整個頁面。用戶體驗好。

歸根結底,仍是由於 SPA 可以提供更好的用戶體驗。前端

爲了更好地實現 SPA,前端路由是必不可少的。假設一個場景:用戶在 SPA 頁面的某個狀態下,點擊了強制刷新按鈕。若是沒有前端路由記住當前狀態,那麼用戶點擊該按鈕以後,就會返回到最開始的頁面狀態。這不是用戶想要的。vue

固然,須要前端路由另外一個點在於:咱們能夠更好地進行 SPA 頁面的管理。經過將組件與路由發生配對關聯,依據路由的層級關係,可爲 SPA 內部的組件劃分與管理提供一個依據參考。ajax

Hash 路由模式 與 History 路由模式

這是兩種常見的前端路由模式。vue-router

Hash 路由模式

Hash 模式使用了瀏覽器 URL 後綴中的#xxx部分來實現前端路由。默認狀況下,URL後綴中的#xxx hash 部分是用來作網頁的錨點功能的,如今前端路由看上了這個點,並對其加以利用。後端

好比這個 URL:http://www.abc.com/#/hello,hash 的值爲 #/helloapi

爲何會看上瀏覽器URL後綴中的 hash 部分呢?緣由也簡單:瀏覽器

  • 瀏覽器URL後綴中的 hash 改變了,不會觸發請求,對服務器徹底沒有影響。它的改變不會從新加載瀏覽器頁面。
  • 更關鍵的一點是,由於hash發生變化的url都會被瀏覽器記錄下來,從而你會發現瀏覽器的前進後退均可以用了,頁面的狀態與瀏覽器的URL就發生了掛鉤。

hash模式背後的原理是onhashchange事件,能夠在window對象上監聽這個事件。服務器

History 路由模式

隨着 HTML5 中 history api 的到來,前端路由開始進化了。hashchange 只能改變 # 後面的代碼片斷,history api (pushState、replaceState、go、back、forward) 則給了前端徹底的自由。簡單講,它的功能更爲強大了:分爲兩大部分,切換和修改。app

路由切換

參考MDN,切換歷史狀態包括 back、forward、go 三個方法,對應瀏覽器的前進,後退,跳轉操做。

history.go(-2);//後退兩次
history.go(2);//前進兩次
history.back(); //後退
hsitory.forward(); //前進
複製代碼

路由修改

修改歷史狀態包括了pushState,replaceState兩個方法:

/** ** 參數含義 ** state: 須要保存的數據,這個數據在觸發popstate事件時,能夠在event.state裏獲取 ** title:標題,基本沒用,通常傳 null ** url:設定新的歷史記錄的 url */	
window.history.pushState(state, title, url) 

//假設當前的url是:https://www.abc.com/a/
//例子1
history.pushState(null, null, './cc/') //此時的url爲https://www.abc.com/a/cc/
//例子2
history.pushState(null, null, '/bb/') //此時的url爲https://www.abc.com/bb/
複製代碼

一樣的,history 模式能夠監聽到對應的事件:

window.addEventListener("popstate", function() {
    // 監聽瀏覽器前進後退事件,pushState 與 replaceState 方法不會觸發 
});
複製代碼

History 模式的注意點

和 Hash 模式相比,History 模式存在着更多的選擇。可是也有一些自身的注意點:在用戶點擊強制刷新的時候,History 模式會向服務器發送請求。

爲了解決這個問題,須要服務器作對應的處理。服務器能夠針對不一樣的URL進行處理,固然,也能夠簡單處理:只要是未匹配到的URL請求,一概返回同一個 index.html 頁面。

Vue Router 作了什麼?

Vue Router 做爲 Vue 生態系統中很是重要的一個成員,它實現了 Vue 應用的路由管理。能夠說,Vue Router 是專門爲 Vue 量身定製的路由管理器,功能點很是多。它的內部實現是與 Vue 自身是有強耦合關係的(Vue Router 內部利用了 Vue 的數據響應式)。

咱們來看一個典型的 Vue Router 配置:

import Vue from "vue";
import App from "./vue/App.vue";
import VueRouter  from 'vue-router';

//以插件的形式,使用VueRouter
Vue.use(VueRouter);

//路由配置信息,能夠從外部文件引入,在此直接寫是爲了方便演示
const Foo = { template: '<div>foo</div>' }
const Bar = { template: '<div>bar</div>' }
const routes = [
  { path: '/', component: Foo },
  { path: '/bar', component: Bar }
]

//初始化並與 Vue 實例關聯
const router = new VueRouter({routes});
new Vue({
  router,
  render: h => h(App),
}).$mount("#root");
複製代碼

可看出,VueRouter 是做爲插件的形式引入到 Vue 系統內部的。而將具體的 router 信息嵌入到每一個 Vue 實例中,則是做爲 Vue 的構造函數參數傳入。

同時來看看如何使用它:

//routerExample.vue
<template>
    <div>
        <h1 @click="goBack">App Test</h1>
        <router-link to="/">foo</router-link>
        <router-link to="/bar">bar</router-link>

        <router-view></router-view>
    </div>
</template>

<script>
export default {
  methods: {
    goBack() {
      console.log(this.$router);  
      window.history.length > 1 ? this.$router.go(-1) : this.$router.push('/')
    }
  }
}
</script>

<style lang="less" scoped>

</style>
複製代碼

上面的代碼中,咱們能夠直接使用router-linkrouter-view這兩個組件。它們是隨着 Vue Router 一塊兒引入的,做爲全局組件使用。

這就是一個最簡單的 Vue Router 的使用方式。咱們下面就來看看,該如何本身實現上面的簡單功能,作一個本身的 Vue Router。

一個簡單的 Vue Router 實現

看了上面的這個過程,最簡單的 Vue Router 應該包括如下實現步驟:

  • 實現 Vue 規定的插件的寫法,將咱們本身的Vue Router 做爲插件引入 Vue 系統中。
    • router功能一:解析傳入的routes選項,以備調用
    • router功能二:監控URL變化(兩種路由方式:history、hash)
  • 實現兩個全局組件:router-linkrouter-view

看看自定義的 Vue Router 的實現:

//FVueRouter.js

 let Vue; //保存 Vue 構造函數的引用,與 Vue 深度綁定

 class FVueRouter {
    constructor(options){
        this.$options = options;
      	//保存路由的路徑與路由組件的對應關係
        this.routerMap = {};

        //當前的URL必須是響應式的,使用一個新的 Vue 實例來實現響應式功能
        this.app = new Vue({
            data: {current : "/"}
        })
    }

    init(){
        //監聽路由事件
        this.bindEvents();
        //解析傳入的routes
        this.createRouterMap();
        //全局組件的聲明
        this.initComponent();
    }

    bindEvents(){
        window.addEventListener('hashchange', this.onHashChange.bind(this));
    }

    onHashChange(){
        this.app.current = window.location.hash.slice(1) || '/';
    }

    createRouterMap(){
        this.$options.routes.forEach(route => {
            this.routerMap[route.path] = route;
        })
    }

    initComponent() {
        // 形式:<router-link to="/"> 轉換目標=> <a href="#/">xxx</a>
        Vue.component("router-link", {
          props: {
            to: String,
          },
          render(h) {
            // h(tag, data, children)
            return h('a', {
                attrs: {href: '#' + this.to}
            }, [this.$slots.default])
          },
        });
        // 獲取path對應的Component將它渲染出來
        Vue.component("router-view", {
            render: (h) => {
                //此處的this 可以正確指向 FVouter內部,是由於箭頭函數
                const Component = this.routerMap[this.app.current].component;
                return h(Component)
            }
        })
      }
 }

 // 全部的插件都須要實現install 方法,傳入參數是Vue的構造函數
 FVueRouter.install = function(_Vue){
    //將Vue的構造函數保存起來
    Vue = _Vue;

    //實現一個混入操做的緣由,插件的install階段很是早,此時並無Vue實例
    //所以,使用mixin,延遲對應操做到Vue實例構建的過程當中來執行。
    Vue.mixin({
        beforeCreate(){
            //獲取到Router的實例,並將其掛載在原型上
            if(this.$options.router){
                //根組件beforeCreate時只執行一次
                Vue.prototype.$router = this.$options.router;

                this.init();
            }
        }
    })
 }

export default FVueRouter;
複製代碼

這裏是最爲簡單的一種實現。有幾個值得注意的點:

  • 如上代碼,將最基本的一個Vue Router 的代碼架子搭建起來了,可以運行。但細微處依然須要酌情考慮。
  • 關於插件的寫法:自定義插件內部必須實現一個 install 方法,傳入參數是Vue的構造函數。
  • 使用了一個新的Vue 實例,將 URL 的 hash 變量進行數據響應化處理。
  • 關於渲染函數 render 的參數 h,它其實是 createElement 函數。具體用法值得深究。代碼中使用的是最爲簡單的處理方式。

結尾

在本文中,咱們講解了 前端路由常見的兩種模式:Hash 模式與 History 模式。同時,咱們嘗試本身實現了一個最爲簡單的 Vue Router。更多相關的 Vue Router 的細節,能夠參考其官網。但願本文對你有用。

相關文章
相關標籤/搜索