亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

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

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

簡化版的vue-router實現思路詳解

發布時間:2020-09-21 11:12:03 來源:腳本之家 閱讀:230 作者:SilentPort 欄目:web開發

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

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 ,前者是用于路由組件的占位,后者用于點擊時跳轉到指定路由。此外組件內部可以通過 this.$router.push , this.$rouer.replace 等api實現路由跳轉。本文將實現上述兩個全局組件以及 push 和 replace 兩個api,調用的時候支持 params 傳參,并且支持 hash 和 history 兩種模式,忽略其余api、嵌套路由、異步路由、 abstract 路由以及導航守衛等高級功能的實現,這樣有助于理解 vue-router 的核心原理。本文的最終代碼不建議在生產環境使用,只做一個學習用途,下面我們就來一步步實現它。

install實現

任何一個 vue 插件都要實現一個 install 方法,通過 Vue.use 調用插件的時候就是在調用插件的 install 方法,那么路由的 install 要做哪些事情呢?首先我們知道 我們會用 new 關鍵字生成一個 router 實例,就像前面的代碼實例一樣,然后將其掛載到根 vue 實例上,那么作為一個全局路由,我們當然需要在各個組件中都可以拿到這個 router 實例。另外我們使用了全局組件 router-view 和 router-link ,由于 install 會接收到 Vue 構造函數作為實參,方便我們調用 Vue.component 來注冊全局組件。因此,在 install 中主要就做兩件事,給各個組件都掛載 router 實例,以及實現 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 對象,它有兩個屬性暴露出來供外界調用,一個是 install ,一個是 Router 構造函數,這樣可以保證插件的正確安裝以及路由實例化。我們先忽略 Router 構造函數,來看 install ,上面代碼中的 this._Vue 是個開始沒有定義的屬性,他的目的是防止多次安裝。我們使用 Vue.mixin 對每個組件的 beforeCreate 鉤子做全局混入,目的是讓每個組件實例共享 router 實例,即通過 this.$router 拿到路由實例,通過 this.$route 拿到路由狀態。需要重點關注的是這行代碼:

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

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

Router構造函數

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

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

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

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

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

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

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

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

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

接下來我們看一下 Router 要做哪些初始化工作。對于 hash 路由而言,url上 hash 值的改變不會引起頁面刷新,但是可以觸發一個 hashchange 事件。由于路由 history.current 初始為 null ,因此匹配不到任何一個路由,所以會導致頁面刷新加載不出任何路由組件。基于這兩點,在 init 方法中,我們需要實現對頁面加載完成的監聽,以及 hash 變化的監聽。對于 history 路由,為了實現瀏覽器前進后退時準確渲染對應組件,還要監聽一個 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;
  }
 })
 }

}

當啟用 hash 模式的時候,我們要檢測url上是否存在 hash 值,沒有的話強制賦值一個默認 path , hash 路由時會根據 hash 值作為 key 來查找路由表。 fixHash 和 getHash 實現如下:

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

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

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

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

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 能夠實現改變url的值但不引起頁面刷新,從而不會導致新請求發生, pushState 會生成一條歷史記錄而 replaceState 不會,后者只是替換當前url。在這兩個方法執行的時候將 path 存入 state ,這就使得 popstate 觸發的時候可以拿到路徑從而觸發組件渲染了。我們在組件內按照如下方式調用,會將 params 寫入 router 實例的 route 屬性中,從而在跳轉后的組件 B 內通過 this.$route.params 可以訪問到傳參。

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

router-link實現

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

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 表示要跳轉的路由路徑, tag 表示 router-link 要渲染的標簽名,默認為標簽。如果是 a 標簽,我們為其添加一個 href 屬性。我們給標簽綁定 click 事件,如果檢測到本次跳轉為當前路由的話什么都不做直接返回,并且阻止默認行為,否者根據 to 更換路由。 hash 模式下并且是 a 標簽時候可以直接利用瀏覽器的默認行為完成url上 hash 的替換,否者重新為 location.hash 賦值。 history 模式下則利用 pushState 去更新url。

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

總結

以上所述是小編給大家介紹的簡化版的vue-router實現思路詳解,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對億速云網站的支持!

向AI問一下細節

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

AI

黔东| 潍坊市| 天津市| 乌拉特中旗| 水富县| 文昌市| 郓城县| 象山县| 大同市| 霍城县| 颍上县| 九龙城区| 仁化县| 旅游| 上饶县| 从化市| 元阳县| 保亭| 乌兰浩特市| 囊谦县| 宕昌县| 广西| 东港市| 翁牛特旗| 蒙城县| 阿拉善左旗| 福清市| 商都县| 望江县| 繁峙县| 九龙坡区| 罗平县| 河池市| 禹城市| 岳阳市| 西安市| 宝应县| 尖扎县| 耿马| 丽水市| 鄄城县|