簡化版的vue-router實現(xiàn)思路詳解

本文旨在介紹 vue-router 的實現(xiàn)思路,并動手實現(xiàn)一個簡化版的 vue-router 。我們先來看一下一般項目中對 vue-router 最基本的一個使用,可以看到,這里定義了四個路由組件,我們只要在根 vue 實例中注入該 router 對象就可以使用了.

創(chuàng)新互聯(lián)2013年至今,先為尖山等服務(wù)建站,尖山等地企業(yè),進(jìn)行企業(yè)商務(wù)咨詢服務(wù)。為尖山企業(yè)網(wǎng)站制作PC+手機(jī)+微官網(wǎng)三網(wǎng)同步一站式服務(wù)解決您的所有建站問題。

import VueRouter from 'vue-router';
import Home from '@/components/Home';
import A from '@/components/A';
import B from '@/components/B'
import C from '@/components/C'
Vue.use(VueRouter)
export default new VueRouter.Router({
 // mode: 'history',
 routes: [
 {
  path: '/',
  component: Home
 },
 {
  path: '/a',
  component: A
 },
 {
  path: '/b',
  component: B
 },
 {
  path: '/c',
  component: C
 }
 ]
})

vue-router 提供兩個全局組件, router-view router-link ,前者是用于路由組件的占位,后者用于點擊時跳轉(zhuǎn)到指定路由。此外組件內(nèi)部可以通過 this.$router.push , this.$rouer.replace 等api實現(xiàn)路由跳轉(zhuǎn)。本文將實現(xiàn)上述兩個全局組件以及 push 和 replace 兩個api,調(diào)用的時候支持 params 傳參,并且支持 hash 和 history 兩種模式,忽略其余api、嵌套路由、異步路由、 abstract 路由以及導(dǎo)航守衛(wèi)等高級功能的實現(xiàn),這樣有助于理解 vue-router 的核心原理。本文的最終代碼不建議在生產(chǎn)環(huán)境使用,只做一個學(xué)習(xí)用途,下面我們就來一步步實現(xiàn)它。

install實現(xiàn)

任何一個 vue 插件都要實現(xiàn)一個 install 方法,通過 Vue.use 調(diào)用插件的時候就是在調(diào)用插件的 install 方法,那么路由的 install 要做哪些事情呢?首先我們知道 我們會用 new 關(guān)鍵字生成一個 router 實例,就像前面的代碼實例一樣,然后將其掛載到根 vue 實例上,那么作為一個全局路由,我們當(dāng)然需要在各個組件中都可以拿到這個 router 實例。另外我們使用了全局組件 router-view 和 router-link ,由于 install 會接收到 Vue 構(gòu)造函數(shù)作為實參,方便我們調(diào)用 Vue.component 來注冊全局組件。因此,在 install 中主要就做兩件事,給各個組件都掛載 router 實例,以及實現(xiàn) router-view router-link 兩個全局組件。下面是代碼:

const install = (Vue) => {
 if (this._Vue) {
 return;
 };
 Vue.mixin({
 beforeCreate() {
  if (this.$options && this.$options.router) {
  this._routerRoot = this;
  this._router = this.$options.router;
  Vue.util.defineReactive(this, '_routeHistory', this._router.history)
  } else {
  this._routerRoot = (this.$parent && this.$parent._routerRoot) || this
  }

  Object.defineProperty(this, '$router', {
  get() {
   return this._routerRoot._router;
  }
  })

  Object.defineProperty(this, '$route', {
  get() {
   return {
   current: this._routerRoot._routeHistory.current,
   ...this._routerRoot._router.route
   };
  }
  })
 }
 });

 Vue.component('router-view', {
 render(h) { ... }
 })

 Vue.component('router-link', { 
 props: {
  to: String,
  tag: String,
 },
 render(h) { ... }
 })
 this._Vue = Vue;
}

這里的 this 代表的就是 vue-router 對象,它有兩個屬性暴露出來供外界調(diào)用,一個是 install ,一個是 Router 構(gòu)造函數(shù),這樣可以保證插件的正確安裝以及路由實例化。我們先忽略 Router 構(gòu)造函數(shù),來看 install ,上面代碼中的 this._Vue 是個開始沒有定義的屬性,他的目的是防止多次安裝。我們使用 Vue.mixin 對每個組件的 beforeCreate 鉤子做全局混入,目的是讓每個組件實例共享 router 實例,即通過 this.$router 拿到路由實例,通過 this.$route 拿到路由狀態(tài)。需要重點關(guān)注的是這行代碼:

Vue.util.defineReactive(this, '_routeHistory', this._router.history)

這行代碼利用 vue 的響應(yīng)式原理,對根 vue 實例注冊了一個 _routeHistory 屬性,指向路由實例的 history 對象,這樣 history 也變成了響應(yīng)式的。因此一旦路由的 history 發(fā)生變化,用到這個值的組件就會觸發(fā) render 函數(shù)重新渲染,這里的組件就是 router-view 。從這里可以窺察到 vue-router 實現(xiàn)的一個基本思路。上述的代碼中對于兩個全局組件的 render 函數(shù)的實現(xiàn),因為會依賴于 router 對象,我們先放一放,稍后再來實現(xiàn)它們,下面我們分析一下 Router 構(gòu)造函數(shù)。

Router構(gòu)造函數(shù)

經(jīng)過剛才的分析,我們知道 router 實例需要有一個 history 對象,需要一個保存當(dāng)前路由狀態(tài)的對象 route ,另外很顯然還需要接受路由配置表 routes ,根據(jù) routes 需要一個路由映射表 routerMap 來實現(xiàn)組件搜索,還需要一個變量 mode 判斷是什么模式下的路由,需要實現(xiàn) push 和 replace 兩個api,代碼如下:

const Router = function (options) {
 this.routes = options.routes; // 存放路由配置
 this.mode = options.mode || 'hash';
 this.route = Object.create(null), // 生成路由狀態(tài)
 this.routerMap = createMap(this.routes) // 生成路由表
 this.history = new RouterHistory(); // 實例化路由歷史對象
 this.init(); // 初始化
}
Router.prototype.push = (options) => { ... }
Router.prototype.replace = (options) => { ... }
Router.prototype.init = () => { ... }

我們看一下路由表 routerMap 的實現(xiàn),由于不考慮嵌套等其他情況,實現(xiàn)很簡單,如下:

const createMap = (routes) => {
 let resMap = Object.create(null);
 routes.forEach(route => {
 resMap[route['path']] = route['component'];
 })
 return resMap;
}

RouterHistory 的實現(xiàn)也很簡單,根據(jù)前面分析,我們只需要一個 current 屬性就可以,如下:

const RouterHistory = function (mode) {
 this.current = null; 
}

有了路由表和 history , router-view 的實現(xiàn)就很容易了,如下:

Vue.component('router-view', {
 render(h) {
  let routerMap = this._self.$router.routerMap;
  return h(routerMap[this._self.$route.current])
 }
 })

這里的 this 是一個 renderProxy 實例,他有一個屬性 _self 可以拿到當(dāng)前的組件實例,進(jìn)而訪問到 routerMap ,可以看到路由實例 history 的 current 本質(zhì)上就是我們配置的路由表中的 path 。

接下來我們看一下 Router 要做哪些初始化工作。對于 hash 路由而言,url上 hash 值的改變不會引起頁面刷新,但是可以觸發(fā)一個 hashchange 事件。由于路由 history.current 初始為 null ,因此匹配不到任何一個路由,所以會導(dǎo)致頁面刷新加載不出任何路由組件?;谶@兩點,在 init 方法中,我們需要實現(xiàn)對頁面加載完成的監(jiān)聽,以及 hash 變化的監(jiān)聽。對于 history 路由,為了實現(xiàn)瀏覽器前進(jìn)后退時準(zhǔn)確渲染對應(yīng)組件,還要監(jiān)聽一個 popstate 事件。代碼如下:

Router.prototype.init = function () {

 if (this.mode === 'hash') {
 fixHash()
 window.addEventListener('hashchange', () => {
  this.history.current = getHash();
 })
 window.addEventListener('load', () => {
  this.history.current = getHash();
 })
 }

 if (this.mode === 'history') {
 removeHash(this);
 window.addEventListener('load', () => {
  this.history.current = location.pathname;
 })
 window.addEventListener('popstate', (e) => {
  if (e.state) {
  this.history.current = e.state.path;
  }
 })
 }

}

當(dāng)啟用 hash 模式的時候,我們要檢測url上是否存在 hash 值,沒有的話強(qiáng)制賦值一個默認(rèn) path , hash 路由時會根據(jù) hash 值作為 key 來查找路由表。 fixHash 和 getHash 實現(xiàn)如下:

const fixHash = () => {
 if (!location.hash) {
 location.hash = '/';
 }
}
const getHash = () => {
 return location.hash.slice(1) || '/';
}

這樣在刷新頁面和 hash 改變的時候, current 可以得到賦值和更新,頁面能根據(jù) hash 值準(zhǔn)確渲染路由。 history 模式也是一樣的道理,只是它通過 location.pathname 作為 key 搜索路由組件,另外 history 模式需要去除url上可能存在的 hash , removeHash 實現(xiàn)如下:

const removeHash = (route) => {
 let url = location.href.split('#')[1]
 if (url) {
 route.current = url;
 history.replaceState({}, null, url)
 }
}

我們可以看到當(dāng)瀏覽器后退的時候, history 模式會觸發(fā) popstate 事件,這個時候是通過 state 狀態(tài)去獲取 path 的,那么 state 狀態(tài)從哪里來呢,答案是從 window.history 對象的 pushState 和 replaceState 而來,這兩個方法正好可以用來實現(xiàn) router 的 push 方法和 replace 方法,我們看一下這里它們的實現(xiàn):

Router.prototype.push = (options) => {
 this.history.current = options.path;
 if (this.mode === 'history') {
 history.pushState({
  path: options.path
 }, null, options.path);
 } else if (this.mode === 'hash') {
 location.hash = options.path;
 }
 this.route.params = {
 ...options.params
 }
}

Router.prototype.replace = (options) => {
 this.history.current = options.path;
 if (this.mode === 'history') {
 history.replaceState({
  path: options.path
 }, null, options.path);
 } else if (this.mode === 'hash') {
 location.replace(`#${options.path}`)
 }
 this.route.params = {
 ...options.params
 }
}

pushState 和 replaceState 能夠?qū)崿F(xiàn)改變url的值但不引起頁面刷新,從而不會導(dǎo)致新請求發(fā)生, pushState 會生成一條歷史記錄而 replaceState 不會,后者只是替換當(dāng)前url。在這兩個方法執(zhí)行的時候?qū)?path 存入 state ,這就使得 popstate 觸發(fā)的時候可以拿到路徑從而觸發(fā)組件渲染了。我們在組件內(nèi)按照如下方式調(diào)用,會將 params 寫入 router 實例的 route 屬性中,從而在跳轉(zhuǎn)后的組件 B 內(nèi)通過 this.$route.params 可以訪問到傳參。

this.$router.push({
 path: '/b',
 params: {
  id: 55
 }
 });

router-link實現(xiàn)

router-view 的實現(xiàn)很簡單,前面已經(jīng)說過。最后,我們來看一下 router-link 的實現(xiàn),先放上代碼:

Vue.component('router-link', { 
 props: {
  to: String,
  tag: String,
 },

 render(h) {
  let mode = this._self.$router.mode;
  let tag = this.tag || 'a';
  let routerHistory = this._self.$router.history;
  return h(tag, {
  attrs: tag === 'a' ? {
   href: mode === 'hash' ? '#' + this.to : this.to,

  } : {},
  on: {
   click: (e) => {
   if (this.to === routerHistory.current) {
    e.preventDefault();
    return;
   }
   routerHistory.current = this.to;
   switch (mode) {
    case 'hash':
    if (tag === 'a') return;
    location.hash = this.to;
    break;
    case 'history':
    history.pushState({
     path: this.to
    }, null, this.to);
    break;
    default:
   }
   e.preventDefault();
   }
  },
  style: {
   cursor: 'pointer'
  }
  }, this.$slots.default)
 }
 })

router-link 可以接受兩個屬性, to 表示要跳轉(zhuǎn)的路由路徑, tag 表示 router-link 要渲染的標(biāo)簽名,默認(rèn)為標(biāo)簽。如果是 a 標(biāo)簽,我們?yōu)槠涮砑右粋€ href 屬性。我們給標(biāo)簽綁定 click 事件,如果檢測到本次跳轉(zhuǎn)為當(dāng)前路由的話什么都不做直接返回,并且阻止默認(rèn)行為,否者根據(jù) to 更換路由。 hash 模式下并且是 a 標(biāo)簽時候可以直接利用瀏覽器的默認(rèn)行為完成url上 hash 的替換,否者重新為 location.hash 賦值。 history 模式下則利用 pushState 去更新url。

以上實現(xiàn)就是一個簡單的vue-router,完整代碼參見vue-router-simple 。

總結(jié)

以上所述是小編給大家介紹的簡化版的vue-router實現(xiàn)思路詳解,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復(fù)大家的。在此也非常感謝大家對創(chuàng)新互聯(lián)網(wǎng)站的支持!

新聞名稱:簡化版的vue-router實現(xiàn)思路詳解
分享URL:http://muchs.cn/article16/picdgg.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)頁設(shè)計公司、全網(wǎng)營銷推廣外貿(mào)建站、動態(tài)網(wǎng)站小程序開發(fā)、營銷型網(wǎng)站建設(shè)

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

成都網(wǎng)站建設(shè)