Vue之vue-router原理剖析

在Vue中,vue-router佔據重要的位置,是vue的核心插件,那麼它的實現原理是什麼呢? 在剖析原理以前,先來了解幾個概念:SAP,路由模式 SPA(single page application):單一頁面應用程序,有且只有一個完整的頁面,當它在加載頁面時,不會加載整個頁面,而只更新某個指定的容器中內容(組件)javascript

路由模式:hash模式、history模式,abstract模式

1.hash模式

隨着ajax的流行,異步數據請求交互運行在不刷新瀏覽器的狀況下進行。而異步交互體驗更高的版本就是SPA--單頁應用。單頁應用不只在頁面交互時不用刷新,並且在頁面跳轉時也是不用刷新,因而就有瀏覽前端路由。實現原理就是根據不一樣的url進行解析,來匹配不一樣的組件;但當url發生變化時就會形成頁面的刷新。這就出現了hash,使用hash在改變url的狀況下,保證頁面的不刷新。 http://www.xxx.com/#/login 這種#,後面hash值發生變化,並不會致使瀏覽器向服務器發出請求,進而不會發生頁面刷新。並且每當hash發生變化時,都會觸發hashchange事件,經過這個事件能夠知道hash值發生了什麼變化,而後能夠監聽hashchange來實現更新頁面部份內容大都操做前端

2.history模式

因在HTML5標準發佈後,多了兩個API,pushStatereplaceState,經過這兩個API能夠改變url地址且不會發送請求。同時還有popstate事件,經過這些API就能以另外一種方式來實現前端路由,但原理與hash實現相同,但不會有**#**,所以當用戶主動刷新頁面之類操做時,仍是會給服務器發送請求,爲避免這種狀況,因此這實現須要服務器支持,需把全部路由都重定向到根頁面vue

3.abstract模式

abstract模式是使用一個不依賴於瀏覽器的瀏覽歷史虛擬管理後端。 根據平臺差別能夠看出,在 Weex 環境中只支持使用 abstract 模式。 不過,vue-router 自身會對環境作校驗,若是發現沒有瀏覽器的 API,vue-router 會自動強制進入 abstract 模式,因此 在使用 vue-router 時只要不寫 mode 配置便可,默認會在瀏覽器環境中使用 hash 模式,在移動端原生環境中使用 abstract 模式。 (固然,你也能夠明確指定在全部狀況下都使用 abstract 模式)java

vue-router實現原理

原理核心:更新視圖而不從新請求頁面。 vue-router實現單頁面跳轉,提供了三種方式:hash模式、history模式 abstract模式,根據mode參決定使用哪一種方式。 接下來詳細剖析其原理 ajax

在這張流程圖中分析vue-router實現原理,咱們將官網的vue-router掛載到Vue實例上,打印出來增長了什麼: vue-router

  • options下的router對象很好理解,這個就是咱們在實例化Vue的時候掛載的那個vue-router實例;
  • _route是一個響應式的路由route對象,這個對象會存儲咱們路由信息,它是經過Vue提供的Vue.util.defineReactive來實現響應式的,下面的get和set即是對它進行的數據劫持;
  • _router存儲的就是咱們從$options中拿到的vue-router對象;
  • _routerRoot指向咱們的Vue根節點;
  • _routerViewCache是咱們對View的緩存;
  • route和router是定義在Vue.prototype上的兩個getter。前者指向_routerRoot下的_route,後者指向_routerRoot下的_router

總結起來,vue-router就是一個類,裏面封裝了一個mixin,定義了兩個‘原型’,註冊了兩個組件。 在mixin中beforeCreate中定義**_routerRoot、_router、_route**,使其每一個組件都有_routerRoot,定義了兩個原型是指在Vue.prototype上面定義的兩個getter,也就是**router、route**,註冊兩個組件是指要用到的router-link、router-view後端

基本原理:數組

  1. Vue.use()時將vue-router掛載到Vue上時調用install方法,在install方法裏調用mixin定義_routerRoot、_router、_route,使其每一個組件都有_routerRoot,_router,在Vue原型上掛載router、route。
  2. 在初始化時調用init方法,根據mode參數決定使用哪一種路由模式;
    • 若是使用hash模式,則定義load頁面加載方法,在頁面加載時經過location.hash獲取地址欄的url的hash值賦值給history對象的current,定義hashchange事件監聽url的hash變化,將變化後的hash值賦值給history對象的current
    • 若是使用的是history模式,則定義load頁面加載函數,經過location.pathname,獲取地址欄url中對應組件的地址如:http://localhost:8080/about,取得 /about,賦值給history對象的current, 定義popstate方法,當點擊前進或後退時改變了當前活動歷史項(history),觸發此事件,經過location.pathname將歷史中的url賦值給history對象的current
  3. 在定義全局組件時,
    • router-link組件,調用render()函數時,經過mode肯定使用哪一種路由方式,獲取傳遞的to賦值給a標籤的href屬性進行跳轉,使用slot插槽傳遞中間的值
    • router-view組件時,將以前賦值的組件名:history.current,經過將數據轉換爲對象方法,傳遞給render()函數中的h()方法進行渲染對應組件 詳細原理已剖析完畢,代碼以下: 目錄結構以下:

app.vue瀏覽器

<template>
    <div id="app">
      <router-link to='/home'>首頁</router-link>
      <br>
      <router-link to="/about">關於</router-link>
      <router-view></router-view>
    </div>
</template>
<script>
export default {
  name:'app'
};
</script>

複製代碼

Home.vue緩存

<template>
    <div>
        home
    </div>
</template>
<script>
export default {
    name:'home'
}
</script>
複製代碼

About.vue

<template>
    <div>
        about
    </div>
</template>
<script>
export default {
    name:'about'
}
</script>
複製代碼

main.js

import Vue from 'vue'
import App from './App.vue'
import router from './routers';
import store from './store'

Vue.config.productionTip = false

new Vue({
  name:'main',
  router,
  store,
  render: h => h(App)
}).$mount('#app')

複製代碼

index.js

import Vue from 'vue';
import routes from './routes';
import VueRouter from './vue-router';

// 使用Vue.use就會調用install方法
Vue.use(VueRouter)

export default new VueRouter({
    mode:"history",//hash,history
    routes
})
複製代碼

routes.js

import Home from '../views/Home.vue';
import About from '../views/About.vue';
export default [
    {path:'/home',name:'home',component:Home},
    {path:'/about',name:'about',component:About}

]
複製代碼

vue-router.js

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路由
            // console.log('使用的是hash模式')
            // console.log(location.hash)
            location.hash?"":location.hash="/"
            window.addEventListener("load",()=>{
                this.history.current=location.hash.slice(1);
                // console.log('load==>',this.history.current)
            })
            window.addEventListener("hashchange",()=>{
                this.history.current=location.hash.slice(1)
                // console.log('hashchange==>',this.history.current)
            })

        }else{
            //使用的history
            location.pathname?"":location.pathname='/'
            window.addEventListener("load",()=>{
                this.history.current=location.pathname;
                // console.log('load==>',this.history.current)
            })
            window.addEventListener("popstate",()=>{
                this.history.current=location.pathname
                // console.log('popstate==>',this.history.current)
            })

        }
    }
    push(){}
    go(){}
    back(){}
    // createMap將數組轉換爲對象結構
    createMap(routes){
        return routes.reduce((memo,current)=>{
            // memo剛開始是個空對象
            memo[current.path]=current.component
            return memo;
        },{})
    }

}

VueRouter.install=function(Vue){
    Vue.mixin({
        // 將每一個組件都混入一個 beforeCreate
        beforeCreate() {
            // 獲取根組件
            if(this.$options&&this.$options.router){
                // 找到根組件
                // 把當前實例掛載到_router上面
                this._router = this.$options.router
                this._root=this;
              Vue.util.defineReactive(this,"xxx",this._router,history)
            }else{
                // main---->app----->home/about 
                this._root = this.$parent._root; //全部組件都有了router
            }
            Object.defineProperty(this,"$router",{
                get(){
                    // console.log(this._root)
                    return this._root._router
                }
            })
            Object.defineProperty(this,"$route",{
                get(){
                    // console.log(this._root._router.history.current)
                    return {
                        current:this._root._router.history.current
                    }
                }
            })

        },
    }),
    Vue.component("router-link",{
        props: {
            to:String
        },
        render(h){
            let mode = this._self._root._router.mode;
            return <a href={mode==='hash'?`#${this.to}`:this.to}>{this.$slots.default}</a>
        }
    }),
    Vue.component("router-view",{
        render(h) {
            let current=this._self._root._router.history.current;
            // console.log(this)
            let routesMap=this._self._root._router.routesMap
            return h(routesMap[current])
        },
    })
}

export default VueRouter
複製代碼
相關文章
相關標籤/搜索