擼一個(gè)簡(jiǎn)單的vue-router來剖析原理

理解

隨著前端業(yè)務(wù)的發(fā)展,
我們一般在寫一個(gè)較為大型的vue項(xiàng)目時(shí)候,會(huì)使用到vue-router,來根據(jù)指定的url或者hash來進(jìn)行內(nèi)容的分發(fā),可以達(dá)到不像服務(wù)端發(fā)送請(qǐng)求,就完成頁(yè)面內(nèi)容的切換,能夠減少像服務(wù)器發(fā)送的請(qǐng)求,讓用戶進(jìn)行頁(yè)面跳轉(zhuǎn)時(shí)候能夠更快,體驗(yàn)更好

疑問

在初學(xué)vue-router的時(shí)候,一般人都會(huì)有一個(gè)印象,router-link以及router-view都是vue原生自帶的標(biāo)簽。但是這個(gè)印象是錯(cuò)誤的,vue-router本質(zhì)上是一個(gè)vue的插件,通過Vue.use(VueRouter)來使用這個(gè)插件。router-link以及router-view也是這個(gè)插件實(shí)現(xiàn)的自定義標(biāo)簽。

本文以開發(fā)插件的模式,擼一個(gè)vue-router插件以加深對(duì)其原理的了解

url變化流程圖解

router流程圖

也就是說,要實(shí)現(xiàn)一個(gè)簡(jiǎn)單的vue-router,需要完成以下需求

具體操作

創(chuàng)建vue項(xiàng)目

vue create my-vue-router

由于只著重于vue-router的內(nèi)容,所以先使用原本的vue-router這樣只替換vue-router源碼文件即可

增加vue-router

vue add router

然后項(xiàng)目目錄就變成了

    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

在目錄中,新建一個(gè)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

此時(shí),@/src/router/index.js中的內(nèi)容里,我們將導(dǎo)入的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

這里我們可以看到,代碼執(zhí)行的流程為
引入myRouter.js->配置routes對(duì)象->new VueRouter->export default導(dǎo)出

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

Vue.use()

vue中的插件,一個(gè)核心的api就是vue.use()

安裝 Vue.js 插件。如果插件是一個(gè)對(duì)象,必須提供 install 方法。如果插件是一個(gè)函數(shù),它會(huì)被作為install 方法。install 方法調(diào)用時(shí),會(huì)將 Vue 作為參數(shù)傳入。
該方法需要在調(diào)用 new Vue() 之前被調(diào)用。

當(dāng) install 方法被同一個(gè)插件多次調(diào)用,插件將只會(huì)被安裝一次。

也就是說,我們?cè)谧约涸斓?code>myRouter里得實(shí)現(xiàn)這個(gè)install方法

需求

  1. 提供一個(gè)構(gòu)造類,能夠使用new VueRouter來生成實(shí)例
  2. 實(shí)現(xiàn)install方法
  3. 監(jiān)聽url變化,并雙向綁定current方法
  4. 注冊(cè)自定義組件router-linkrouter-view
  5. 實(shí)現(xiàn)用戶配置的路由數(shù)組到map的轉(zhuǎn)換,方便快速的查詢到路由匹配的對(duì)象

實(shí)現(xiàn)

let Vue;//由于使用者肯定是使用vue.use引入的這個(gè)插件,所以代碼里就不引入vue.js了,防止重復(fù)打包
// 需求1 聲明一個(gè)擁有constructor構(gòu)造器的class
class VueRouter{
    constructor(options={}){// 構(gòu)造函數(shù)
        this.$options=options;// 保存配置項(xiàng)
        this.app = { // 聲明一個(gè)擁有current的變量,已完成路由的雙向綁定
            current:"/"
        }
        Vue.util.defineReactive(this.app,'current',this.app.current);//vue的攔截方法,會(huì)該值增加get攔截以收集依賴,set攔截以觸發(fā)雙向綁定
        this.routerMap={}; // 創(chuàng)建key-value模式的routerMap,便于使用key能夠快速的找到即將render(渲染)的組件
        this.init(options); // 執(zhí)行init方法,以完成需求3,4,5
    }
    init(options={}){
        this.bindBrowserEvents()// 綁定瀏覽器事件
        this.initComponent()//注冊(cè)router-view及router-link組件
        this.createRouterMap(options.routes)//創(chuàng)建key-value模式的routerMap
    }
    createRouterMap(arr=[]){ // 創(chuàng)建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模式監(jiān)聽 hashchange 方法
        window.addEventListener('load',this.onHashChange.bind(this))
        window.addEventListener('hashchange',this.onHashChange.bind(this))
    }
    initComponent(){ // 注冊(cè)自定義組件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變化時(shí),改變 this.app.current 
        window.location.hash = window.location.hash || '/'; // 如果hash沒有值,則默認(rèn)給補(bǔ)一個(gè)/#/
        if(this.routerMap[window.location.hash.slice(1)]){ // this.app.current = hash值
            this.app.current = window.location.hash.slice(1);
        }else{
            this.app.current = '/';
        }

        // 此處執(zhí)行完后,則由于雙向綁定,會(huì)觸發(fā)routerView進(jìn)行重新渲染
    }
}

// 需求2 實(shí)現(xiàn)install方法
VueRouter.install = function(_Vue){
  Vue = _Vue; // 因?yàn)橐欢〞?huì)先走install,所以將這個(gè)傳入的Vue實(shí)例,保存到變量Vue中
}

注釋都寫在代碼里啦,可以執(zhí)行簡(jiǎn)單的路由雙向綁定功能,有哪里有疑問可以提出互相學(xué)習(xí)

覺得好的話,可以給我的 github點(diǎn)個(gè)star

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 這是一篇集合了從如何查看 vue-router源碼(v3.1.3),到 vue-router源碼解析,以及擴(kuò)展了相...
    尤小小閱讀 5,718評(píng)論 2 14
  • 回憶: 我們知道,h5的history或者h(yuǎn)ash幫助我們解決了,變化url跳轉(zhuǎn)頁(yè)面不發(fā)送請(qǐng)求,并且我們能監(jiān)聽到u...
    LoveBugs_King閱讀 3,834評(píng)論 0 5
  • SPA單頁(yè)應(yīng)用 傳統(tǒng)的項(xiàng)目大多使用多頁(yè)面結(jié)構(gòu),需要切換內(nèi)容的時(shí)候我們往往會(huì)進(jìn)行單個(gè)html文件的跳轉(zhuǎn),這個(gè)時(shí)候受網(wǎng)...
    視覺派Pie閱讀 12,211評(píng)論 1 55
  • 一、vue-router實(shí)現(xiàn)原理 SPA(single page application):單一頁(yè)面應(yīng)用程序,只有...
    walycode閱讀 1,132評(píng)論 1 3
  • 前言 Vue 是一個(gè)漸進(jìn)式的框架,這意味著你可以只使用 Vue 的核心庫(kù)來開發(fā),但是當(dāng)你在開發(fā)一個(gè)完整的業(yè)務(wù)項(xiàng)目時(shí)...
    心_c2a2閱讀 1,557評(píng)論 0 5

友情鏈接更多精彩內(nèi)容