溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

教你手寫一個簡易的 Vue Router

發(fā)布時間:2020-10-28 18:59:58 來源:億速云 閱讀:174 作者:Leah 欄目:開發(fā)技術(shù)

教你手寫一個簡易的 Vue Router?很多新手對此不是很清楚,為了幫助大家解決這個難題,下面小編將為大家詳細講解,有這方面需求的人可以來學習下,希望你能有所收獲。

基本骨架

  • Vue 里面使用插件的方式是 Vue.use(plugin) ,這里貼出它的用法:
     

安裝 Vue.js 插件。如果插件是一個對象,必須提供 install 方法。如果插件是一個函數(shù),它會被作為 install 方法。install 方法調(diào)用時,會將 Vue 作為參數(shù)傳入。這個方法的第一個參數(shù)是 Vue 構(gòu)造器,第二個參數(shù)是一個可選的選項對象。

  • 全局混入

使用 Vue.mixin(mixin)

全局注冊一個混入,影響注冊之后所有創(chuàng)建的每個 Vue 實例??梢允褂没烊胂蚪M件注入自定義的行為,它將影響每一個之后創(chuàng)建的 Vue 實例。

  • 路由用法
     

比如簡單的:

// 路由數(shù)組
const routes = [
 {
  path: '/',
  name: 'Page1',
  component: Page1,
 },
 {
  path: '/page2',
  name: 'Page2',
  component: Page2,
 },
]

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

它是傳入了moderoutes,我們實現(xiàn)的時候需要在VueRouter構(gòu)造函數(shù)中接收。

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

<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('h2', {}, '視圖顯示的地方') // 暫時置為h2標簽,下面會改
  },
 })
}

export default VueRouter

mode

vue-router有兩種模式,默認為 hash 模式。

history 模式

通過window.history.pushStateAPI 來添加瀏覽器歷史記錄,然后通過監(jiān)聽popState事件,也就是監(jiān)聽歷史記錄的改變,來加載相應的內(nèi)容。

  • popstate 事件

當活動歷史記錄條目更改時,將觸發(fā) popstate 事件。如果被激活的歷史記錄條目是通過對 history.pushState()的調(diào)用創(chuàng)建的,或者受到對 history.replaceState()的調(diào)用的影響,popstate 事件的 state 屬性包含歷史條目的狀態(tài)對象的副本。

  • History.pushState()方法
     

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

該方法用于在歷史中添加一條記錄,接收三個參數(shù),依次為:

  • state:一個與添加的記錄相關(guān)聯(lián)的狀態(tài)對象,主要用于popstate事件。該事件觸發(fā)時,該對象會傳入回調(diào)函數(shù)。也就是說,瀏覽器會將這個對象序列化以后保留在本地,重新載入這個頁面的時候,可以拿到這個對象。如果不需要這個對象,此處可以填null。
  • title:新頁面的標題。但是,現(xiàn)在所有瀏覽器都忽視這個參數(shù),所以這里可以填空字符串。
  • url:新的網(wǎng)址,必須與當前頁面處在同一個域。瀏覽器的地址欄將顯示這個網(wǎng)址。

hash 模式

使用 URL 的 hash 來模擬一個完整的 URL。,通過監(jiān)聽hashchange事件,然后根據(jù)hash值(可通過 window.location.hash 屬性讀?。┤ゼ虞d對應的內(nèi)容的。

繼續(xù)增加代碼,

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() // 初始化路由函數(shù)
 }

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

 initRoute() {
  if (this.mode === 'hash') {
   // 先判斷用戶打開時有沒有hash值,沒有的話跳轉(zhuǎn)到 #/
   location.hash &#63; '' : (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 &#63; '' : (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) // 監(jiān)聽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', {
  props: {
   to: {
    type: [String, Object],
    required: true,
   },
   tag: {
    type: String,
    default: 'a',
   },
  },
  methods: {
   handleClick(event) {
    // 阻止a標簽默認跳轉(zhuǎn)
    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' &#63; '#' + 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)
  },
 })

 Vue.component('router-view', {
  render(h) {
   let current = this._self._root._router.history.current // current已經(jīng)是動態(tài)響應
   let routesMap = this._self._root._router.routesMap
   return h(routesMap[current]) // 動態(tài)渲染對應組件
  },
 })
}

至此,一個簡易的vue-router就實現(xiàn)完了,案例完整代碼附上:

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() // 當前路由
  // 初始化路由函數(shù)
  this.initRoute()
 }

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

 initRoute() {
  if (this.mode === 'hash') {
   // 先判斷用戶打開時有沒有hash值,沒有的話跳轉(zhuǎn)到 #/
   location.hash &#63; '' : (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 &#63; '' : (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 // 把當前vue實例保存到_root上
    this._router = this.$options.router // 把router的實例掛載在_router上
    Vue.util.defineReactive(this, '_route', this._router.history) // 監(jiān)聽history路徑變化
   } else if (this.$parent && this.$parent._root) {
    // 子組件的話就去繼承父組件的實例,讓所有組件共享一個router實例
    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
    },
   })
  },
 })

 Vue.component('router-link', {
  props: {
   to: {
    type: [String, Object],
    required: true,
   },
   tag: {
    type: String,
    default: 'a',
   },
  },
  methods: {
   handleClick(event) {
    // 阻止a標簽默認跳轉(zhuǎn)
    event && event.preventDefault && event.preventDefault() // 阻止a標簽默認跳轉(zhuǎn)
    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(0))
    }
   },
  },
  render(h) {
   let mode = this._self._root._router.mode
   let tag = this.tag || 'a'
   let to = mode === 'hash' &#63; '#' + this.to : 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)
  },
 })

 Vue.component('router-view', {
  render(h) {
   let current = this._self._root._router.history.current // current已經(jīng)是動態(tài)
   let routesMap = this._self._root._router.routesMap
   return h(routesMap[current]) // 動態(tài)渲染對應組件
  },
 })
}

export default VueRouter

看完上述內(nèi)容是否對您有幫助呢?如果還想對相關(guān)知識有進一步的了解或閱讀更多相關(guān)文章,請關(guān)注億速云行業(yè)資訊頻道,感謝您對億速云的支持。

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI