實現一個Vue-router插件

Vue插件開發

我們開發的插件需要實現一個install方法,它接收的第一個參數是Vue的構造器;通過全局方法 Vue.use() 使用插件。它需要在你調用 new Vue() 啓動應用之前完成。

hash模式和history模式

在html5的history模式出現以前,前段路由的實現基本都是使用hash來實現的,它能兼容到IE8。hash指的是url中’#'號及其後面的字符,也稱作錨點,可以使對應的元素顯示在可視區域內。當hash值變化時,瀏覽器不會向服務器發出請求。監聽hash的變化,可以使用hashchange事件。vue-router默認使用hash模式。

history模式在URL裏面不會有一個’#'號,html5規範提供了history.pushState和history.replaceState來進行路由控制,也不會向服務器發送請求,它只能兼容到IE10。

模仿vue-router插件

我們需要實現一個使用hash模式的路由插件,它要註冊兩個全局組件,k-router-link和k-router-view,並且能監聽hash的變化,將對應的dom顯示到頁面上。

代碼實現

  • 配置一個路由文件

    import Vue from 'vue'
    import HelloWorld from '@/components/HelloWorld.vue'
    import About from '@/components/About.vue'
    // 導入我們實現的router組件
    import KVueRouter from './KVueRouter'
    
    // use方法安裝KVueRouter插件,如果KVueRouter是個對象,需要實現一個install方法
    Vue.use(KVueRouter)
    
    // 配置路由選項
    const router1 = [
      {
        path: '/',
        name: 'home',
        component: HelloWorld
      },
      {
        path: '/about',
        name: 'about',
        component: About
      }
    ]
    
    export default new KVueRouter({
      router1
    })
    
  • 在KVueRouter.js文件中,創建並導出一個KVueRouter類,實現install方法

    import KRouterLink from './KRouterLink.js'
    import KRouterView from './KRouterView.js'
    
    // 聲明一個變量來接收install方法中收到的Vue構造函數
    let Vue
    
    // new KVueRouter的時候會調用constructor方法
    class KVueRouter {
      constructor(options) {
        // options就是配置的路由信息,將它作爲KVueRouter對象的一個屬性
        this.$options = options
        // 創建一個響應式數據,來存儲當前的路由信息,在KRouterView組件中可以直接用這個變量
        // defineReactive()方法是vue創建響應式數據的方法,這裏是在KVueRouter對象上面創建一個名爲current的響應式屬性,初始值是'/'
    
         Vue.util.defineReactive(this, 'current', '/')
    
        // 使用hashchange事件來監聽當前路由的變化,它監聽的是當前連接的錨部分(就是 # 後面的)的變化
        // 使用bind方法防止this指向發生變化
        window.addEventListener('hashchange', this.onHashChange.bind(this))
        window.addEventListener('load', this.onHashChange.bind(this))
    
        // 生成一個map,方便view組件獲取當前路由對應的組件
        this.routerMap = {}
        this.$options.router1.forEach(route => {
          this.routerMap[route.path] = route.component
        })
      }
      onHashChange () {
        // window.location.hash就是url中錨部分,但是它以# 開頭,需要把#去掉
        this.current = window.location.hash.slice(1)
        this.match = []
        this.matchRouter()
      }
    }
    
    // 實現Vue.use()需要的install方法
    // Vue.use()方法會把Vue作爲參數傳到install方法中來
    KVueRouter.install = function (_vue) {
      Vue = _vue
      // 此時收到的Vue是個構造函數,並不是根實例,這裏需要把根實例中的router選項掛載到Vue原型鏈上,這樣每個組件都可以通過this.$router來訪問router
      // 只能利用全局混入,在beforeCreate()這個聲明週期鉤子中獲取router選項
      Vue.mixin({
        // beforeCreate鉤子在每個組件實例化的時候去執行,但是router選項只在根實例中有,所以要判斷一下
        beforeCreate () {
          // 這裏this指向當前正在實例化的組件,this.$options是組件的初始化選項
          if (this.$options.router) {
            Vue.prototype.$router = this.$options.router
          }
        }
      })
    
      // 再聲明兩個全局組件,link和view
      Vue.component('k-router-link', KRouterLink)
      Vue.component('k-router-view', KRouterView)
    }
    
    export default KVueRouter
    
  • 實現k-router-link組件

    // 導出一個對象,這個對象是link組件的配置信息
    // 這裏渲染一個<a href="/a/b"></a>
    // 這個組件從父組件接收一個to屬性,來判斷自己的地址
    export default {
      props: {
        to: {
          type: String,
          required: true
        }
      },
      // 使用render函數
      render (h) {
        // h其實是createElement(),它接受三個參數: 標籤名稱、屬性集合、子元素數組
        // 我們使用前端hash路由模式來實現一個單頁面應用,需要在接收到的路由字符串前邊拼接一個'#'
        // this.$slots存放了插槽的內容
        return h('a', { attrs: { href: '#' + this.to } }, [this.$slots.default])
        // 這裏還可以使用jsx語法,但是有使用的限制,vue-cli支持這種寫法
        // return <a href={'#' + this.to}>{this.$slots.default}</a>
      }
    }
    
  • 實現k-router-view組件

    // 這個組件就是獲取當前路由對應的組件,拿到這個組件對象,並渲染到頁面中
    export default {
      // 只有一層路由的情況
      render (h) {
        let component = null
        // 通過this.$router.routerMap獲取創建的路由map,通過this.$router.current獲取當前鏈接
        let route = this.$router.routerMap[this.$router.current]
        if (route) {
          component = route
        }
        return h(component)
      }
    }
    

使用

和使用router-view一樣,我們在main.js中導入路由,並注入到根實例中,這樣整個應用都擁有了路由功能

main.js

import Vue from 'vue'
import App from './App.vue'
import router from './kvuerouter/index.js'

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

App.vue

<template>
  <div id="app">
    <k-router-link to="/">router home |</k-router-link>
    <k-router-link to="/about">router about</k-router-link>
    <k-router-view></k-router-view>
  </div>
</template>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章