沐鳴總代_寫一個簡單的vue-router來剖析原理

理解

隨着前端業務的發展, 我們一般在寫一個較為大型的vue項目時候,會使用到vue-router,來根據指定的url或者hash來進行內容的分發,可以達到不像服務端發送請求,就完成頁面內容的切換,能夠減少像服務器發送的請求,讓用戶進行頁面跳轉時候能夠更快,體驗更好

疑問

在初學vue-router的時候,一般人都會有一個印象,router-link以及router-view都是vue原生自帶的標籤。但是這個印象是錯誤的,vue-router本質上是一個vue的插件,通過Vue.use(VueRouter)來使用這個插件。router-link以及router-view也是這個插件實現的自定義標籤。

本文以開發插件的模式,擼一個vue-router插件以加深對其原理的了解

url變化流程圖解

也就是說,要實現一個簡單的vue-router,需要完成以下需求

具體操作

創建vue項目

vue create my-vue-router

由於只着重於vue-router的內容,所以先使用原本的vue-router這樣只替換vue-router源碼文件即可

增加vue-router

vue add router

然後項目目錄就變成了

    my-vue-router
      |- node_modules
      |- public
      |- src
          |- assets
          |- components
              |- HellowWorld.vue
          |- router
              |- index.js
          |- views
              |- About.vue
              |- Home.vue
          |- App.vue
          |- main.js
      |- .gitinore
      |- babel.config.js
      |- package.json
      |- README.md
      |- yarn.lock

在目錄中,新建一個myRouter.js的文件,來放置我們的源碼

新建自己的myRouter文件

my-vue-router
      |- node_modules
      |- public
      |- src
          |- assets
          |- components
              |- HellowWorld.vue
          |- router
              |- index.js
+             |- myRouter.js            
          |- views
              |- About.vue
              |- Home.vue
          |- App.vue
          |- main.js
      |- .gitinore
      |- babel.config.js
      |- package.json
      |- README.md
      |- yarn.lock

切換引入文件為自己寫的myRouter.js

此時,@/src/router/index.js中的內容里,我們將導入的vue-router替換為我們的myRouter.js

  import Vue from 'vue'
- import VueRouter from 'vue-router'
+ import VueRouter from './myRouter'
  import Home from '../views/Home.vue'

  Vue.use(VueRouter)

  const routes = [
    {
      path: '/',
      name: 'Home',
      component: Home
    },
    {
      path: '/about',
      name: 'About',
      // route level code-splitting
      // this generates a separate chunk (about.[hash].js) for this route
      // which is lazy-loaded when the route is visited.
      component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
    }
  ]

  const router = new VueRouter({
    mode: 'history',
    base: process.env.BASE_URL,
    routes
  })

  export default router

這裏我們可以看到,代碼執行的流程為 引入myRouter.js->配置routes對象->new VueRouter->export default 導出

此處用到了 Vue.use()這個API

Vue.use()

vue中的插件,一個核心的api就是vue.use() > 安裝 Vue.js 插件。如果插件是一個對象,必須提供 install 方法。如果插件是一個函數,它會被作為install 方法。install 方法調用時,會將 Vue 作為參數傳入。 該方法需要在調用 new Vue() 之前被調用。 當 install 方法被同一個插件多次調用,插件將只會被安裝一次。

也就是說,我們在自己造的myRouter里得實現這個install方法

需求

  1. 提供一個構造類,能夠使用new VueRouter來生成實例
  2. 實現install方法
  3. 監聽url變化,並雙向綁定current方法
  4. 註冊自定義組件router-link與router-view
  5. 實現用戶配置的路由數組到map的轉換,方便快速的查詢到路由匹配的對象

實現

let Vue;//由於使用者肯定是使用vue.use引入的這個插件,所以代碼里就不引入vue.js了,防止重複打包
// 需求1 聲明一個擁有constructor構造器的class
class VueRouter{
    constructor(options={}){// 構造函數
        this.$options=options;// 保存配置項
        this.app = { // 聲明一個擁有current的變量,已完成路由的雙向綁定
            current:"/"
        }
        Vue.util.definereactive(this.app,'current',this.app.current);//vue的攔截方法,會該值增加get攔截以收集依賴,set攔截以觸發雙向綁定
        this.routerMap={}; // 創建key-value模式的routerMap,便於使用key能夠快速的找到即將render(渲染)的組件
        this.init(options); // 執行init方法,以完成需求3,4,5
    }
    init(options={}){
        this.bindBrowserEvents()// 綁定瀏覽器事件
        this.initComponent()//註冊router-view及router-link組件
        this.createRouterMap(options.routes)//創建key-value模式的routerMap
    }
    createRouterMap(arr=[]){ // 創建routerMap
        arr.forEach(item => {
            this.routerMap[item.path]=item
        });
        //  處理完后routerMap格式如下
        //  this.routerMap = {
        //    '/':{
        //      path: '/',
        //      name: 'Home',
        //      component: Home
        //    },
        //    '/about':{
        //      path: '/about',
        //      name: 'About',
        //      component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
        //    }
        //  }
    }
    bindBrowserEvents(){ // hash模式監聽 hashchange 方法
        window.addEventListener('load',this.onHashChange.bind(this))
        window.addEventListener('hashchange',this.onHashChange.bind(this))
    }
    initComponent(){ // 註冊自定義組件RouterLink及RouterView
        Vue.component('RouterLink',{
            props: {
                to: String
            },
            render(h) {
                return h('a',{
                    attrs:{
                        href:'#'+this.to
                    }
                },this.$slots.default)
            },
        })
        Vue.component('RouterView',{
            render:(h)=>{
                const component = this.routerMap[this.app.current].component
                return h(component)
            },
        })
    }
    onHashChange(){ // hash變化時,改變 this.app.current 
        window.location.hash = window.location.hash || '/'; // 如果hash沒有值,則默認給補一個/#/
        if(this.routerMap[window.location.hash.slice(1)]){ // this.app.current = hash值
            this.app.current = window.location.hash.slice(1);
        }else{
            this.app.current = '/';
        }

        // 此處執行完后,則由於雙向綁定,會觸發routerView進行重新渲染
    }
}

// 需求2 實現install方法
VueRouter.install = function(_Vue){
  Vue = _Vue; // 因為一定會先走install,所以將這個傳入的Vue實例,保存到變量Vue中
}

註釋都寫在代碼里啦,可以執行簡單的路由雙向綁定功能,有哪裡有疑問可以提出~互相學習~

覺得好的話,可以給我的 github點個star哦

站長推薦

1.雲服務推薦: 國內主流雲服務商,各類雲產品的最新活動,優惠券領取。地址:阿里雲騰訊雲華為雲

2.廣告聯盟: 整理了目前主流的廣告聯盟平台,如果你有流量,可以作為參考選擇適合你的平台點擊進入

鏈接: http://www.fly63.com/article/detial/8137