1. 程式人生 > 程式設計 >如何手寫簡易的 Vue Router

如何手寫簡易的 Vue Router

前言

還是那樣,懂得如何使用一個常用庫,還得了解其原理或者怎麼模擬實現,今天實現一下 vue-router 。

有一些知識我這篇文章提到了,這裡就不詳細一步步寫,請看我 手寫一個簡易的 Vuex

基本骨架

  • Vue 裡面使用外掛的方式是 Vue.use(plugin) ,這裡貼出它的用法:

安裝 Vue.js 外掛。如果外掛是一個物件,必須提供 install 方法。如果外掛是一個函式,它會被作為 install 方法。install 方法呼叫時,會將 Vue 作為引數傳入。這個方法的第一個引數是 Vue 構造器,第二個引數是一個可選的選項物件。

  • 全域性混入

使用 Vue.mixin(mixin)

全域性註冊一個混入,影響註冊之後所有建立的每個 Vue 例項。可以使用混入向元件注入自定義的行為,它將影響每一個之後建立的 Vue 例項。

  • 路由用法

比如簡單的:

// 路由陣列
const routes = [
 {
  path: '/',name: 'Page1',component: Page1,},{
  path: '/page2',name: 'Page2',component: Page2,]

const router = new VueRouter({
 mode: 'history',// 模式
 routes,})

它是傳入了moderoutes,我們實現的時候需要在VueRouter

建構函式中接收。

在使用路由標題的時候是這樣:

<p>
 <!-- 使用 router-link 元件來導航. -->
 <!-- 通過傳入 `to` 屬性指定連結. -->
 <!-- <router-link> 預設會被渲染成一個 `<a>` 標籤 -->
 <router-link to="/page1">Go to Foo</router-link>
 <router-link to="/page2">Go to Bar</router-link>
</p>
<!-- 路由出口 -->
<!-- 路由匹配到的元件將渲染在這裡 -->
<router-view></router-view>

故我們需要使用Vue.component( id,[definition] )註冊一個全域性元件。

瞭解了大概,我們就可以寫出一個基本骨架

let Vue = null

class VueRouter {
 constructor(options) {
  this.mode = options.mode || 'hash'
  this.routes = options.routes || []
 }
}

VueRouter.install = function (_Vue) {
 Vue = _Vue

 Vue.mixin({
  beforeCreate() {
   // 根元件
   if (this.$options && this.$options.router) {
    this._root = this // 把當前vue例項儲存到_root上
    this._router = this.$options.router // 把router的例項掛載在_router上
   } else if (this.$parent && this.$parent._root) {
    // 子元件的話就去繼承父元件的例項,讓所有元件共享一個router例項
    this._root = this.$parent && this.$parent._root
   }
  },})

 Vue.component('router-link',{
  props: {
   to: {
    type: [String,Object],required: true,tag: {
    type: String,default: 'a',// router-link 預設渲染成 a 標籤
   },render(h) {
   let tag = this.tag || 'a'
   return <tag href={this.to}>{this.$slots.default}</tag>
  },})

 Vue.component('router-view',{
  render(h) {
   return h('h1',{},'檢視顯示的地方') // 暫時置為h1標籤,下面會改
  },})
}

export default VueRouter

mode

vue-router有兩種模式,預設為 hash 模式。

history 模式

通過window.history.pushStateAPI 來新增瀏覽器歷史記錄,然後通過監聽popState事件,也就是監聽歷史記錄的改變,來載入相應的內容。

  • popstate 事件

當活動歷史記錄條目更改時,將觸發 popstate 事件。如果被啟用的歷史記錄條目是通過對 history.pushState()的呼叫建立的,或者受到對 history.replaceState()的呼叫的影響,popstate 事件的 state 屬性包含歷史條目的狀態物件的副本。

  • History.pushState()方法

window.history.pushState(state,title,url)

該方法用於在歷史中新增一條記錄,接收三個引數,依次為:

  • state:一個與新增的記錄相關聯的狀態物件,主要用於popstate事件。該事件觸發時,該物件會傳入回撥函式。也就是說,瀏覽器會將這個物件序列化以後保留在本地,重新載入這個頁面的時候,可以拿到這個物件。如果不需要這個物件,此處可以填null。
  • title:新頁面的標題。但是,現在所有瀏覽器都忽視這個引數,所以這裡可以填空字串。
  • url:新的網址,必須與當前頁面處在同一個域。瀏覽器的位址列將顯示這個網址。

hash 模式

使用 URL 的 hash 來模擬一個完整的 URL。,通過監聽hashchange事件,然後根據hash值(可通過 window.location.hash 屬性讀取)去載入對應的內容的。

繼續增加程式碼,

let Vue = null

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.initRoute() // 初始化路由函式
 }

 createMap(routes) {
  return routes.reduce((pre,current) => {
   pre[current.path] = current.component
   return pre
  },{})
 }

 initRoute() {
  if (this.mode === 'hash') {
   // 先判斷使用者開啟時有沒有hash值,沒有的話跳轉到 #/
   location.hash ? '' : (location.hash = '/')
   window.addEventListener('load',() => {
    this.history.current = location.hash.slice(1)
   })
   window.addEventListener('hashchange',() => {
    this.history.current = location.hash.slice(1)
   })
  } else {
   // history模式
   location.pathname ? '' : (location.pathname = '/')
   window.addEventListener('load',() => {
    this.history.current = location.pathname
   })
   window.addEventListener('popstate',() => {
    this.history.current = location.pathname
   })
  }
 }
}

VueRouter.install = function (_Vue) {
 Vue = _Vue

 Vue.mixin({
  beforeCreate() {
   if (this.$options && this.$options.router) {
    this._root = this
    this._router = this.$options.router
    Vue.util.defineReactive(this,'_route',this._router.history) // 監聽history路徑變化
   } else if (this.$parent && this.$parent._root) {
    this._root = this.$parent && this.$parent._root
   }
   // 當訪問this.$router時即返回router例項
   Object.defineProperty(this,'$router',{
    get() {
     return this._root._router
    },})
   // 當訪問this.$route時即返回當前頁面路由資訊
   Object.defineProperty(this,'$route',{
    get() {
     return this._root._router.history.current
    },})
  },})
}

export default VueRouter

router-link 和 router-view 元件

VueRouter.install = function (_Vue) {
 Vue = _Vue

 Vue.component('router-link',methods: {
   handleClick(event) {
    // 阻止a標籤預設跳轉
    event && event.preventDefault && event.preventDefault()
    let mode = this._self._root._router.mode
    let path = this.to
    this._self._root._router.history.current = path
    if (mode === 'hash') {
     window.history.pushState(null,'','#/' + path.slice(1))
    } else {
     window.history.pushState(null,path.slice(1))
    }
   },render(h) {
   let mode = this._self._root._router.mode
   let tag = this.tag || 'a'
   let to = mode === 'hash' ? '#' + this.to : this.to
   console.log('render',this.to)
   return (
    <tag on-click={this.handleClick} href={to}>
     {this.$slots.default}
    </tag>
   )
   // return h(tag,{ attrs: { href: to },on: { click: this.handleClick } },this.$slots.default)
  },{
  render(h) {
   let current = this._self._root._router.history.current // current已經是動態響應
   let routesMap = this._self._root._router.routesMap
   return h(routesMap[current]) // 動態渲染對應元件
  },})
}

至此,一個簡易的vue-router就實現完了,案例完整程式碼附上:

let Vue = null

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.initRoute()
 }

 createMap(routes) {
  return routes.reduce((pre,() => {
    this.history.current = location.pathname
   })
  }
 }
}

VueRouter.install = function(_Vue) {
 Vue = _Vue

 Vue.mixin({
  beforeCreate() {
   // 根元件
   if (this.$options && this.$options.router) {
    this._root = this // 把當前vue例項儲存到_root上
    this._router = this.$options.router // 把router的例項掛載在_router上
    Vue.util.defineReactive(this,this._router.history) // 監聽history路徑變化
   } else if (this.$parent && this.$parent._root) {
    // 子元件的話就去繼承父元件的例項,讓所有元件共享一個router例項
    this._root = this.$parent && this.$parent._root
   }
   // 當訪問this.$router時即返回router例項
   Object.defineProperty(this,methods: {
   handleClick(event) {
    // 阻止a標籤預設跳轉
    event && event.preventDefault && event.preventDefault() // 阻止a標籤預設跳轉
    let mode = this._self._root._router.mode
    let path = this.to
    this._self._root._router.history.current = path
    if (mode === 'hash') {
     window.history.pushState(null,path.slice(0))
    }
   },render(h) {
   let mode = this._self._root._router.mode
   let tag = this.tag || 'a'
   let to = mode === 'hash' ? '#' + this.to : this.to
   return (
    <tag on-click={this.handleClick} href={to}>
     {this.$slots.default}
    </tag>
   )
   // return h(tag,{
  render(h) {
   let current = this._self._root._router.history.current // current已經是動態
   let routesMap = this._self._root._router.routesMap
   return h(routesMap[current]) // 動態渲染對應元件
  },})
}

export default VueRouter

ps: 個人技術博文 Github 倉庫,覺得不錯的話歡迎 star,給我一點鼓勵繼續寫作吧~

以上就是如何手寫簡易的 Vue Router的詳細內容,更多關於手寫簡易的 Vue Router的資料請關注我們其它相關文章!