擼一個簡單的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

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

url變化流程圖解

router流程圖

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

具體操做

建立vue項目

vue create my-vue-router

因爲只着重於vue-router的內容,因此先使用本來的vue-router這樣只替換vue-router源碼文件便可git

增長vue-routergithub

vue add router

而後項目目錄就變成了web

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的文件,來放置咱們的源碼vue-router

新建本身的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.jsjson

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導出api

此處用到了 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-linkrouter-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

相關文章
相關標籤/搜索