來,咱們手寫一個簡易版的mock.js吧(模擬fetch && Ajax請求)

預期的mock的使用方式

首先咱們從使用的角度出發,思考編碼過程webpack

  • M1. 經過配置文件配置url和responsegit

  • M2. 自動檢測環境爲開發環境時啓動Mock.jsgithub

  • M3. mock代碼能直接覆蓋global.fetch方法或者XMLHttpRequest構造函數,實現開發無感知web

  • M4. mock配置不影響實際的請求,可無縫切換爲實際請求ajax

M1. 經過配置文件配置url和response

比較符合咱們使用習慣的,也許是下面這種mock方式,有一個專門的配置文件,管理請求的url和返回值。每一個請求對應輸出數組中的一個對象,對象的rule屬性能夠是一個字符串或者一個正則表達式,用來匹配url,對象的res屬性則是咱們但願的從中請求中拿到的返回的數據 (也許這裏面還應該加個type表示請求的類型,可是我這個是mock的最簡化版,因此就不加了)正則表達式

// api.js
module.exports = [
  {
    rule: '/mock',
    res: {
      a: 'data',
      b: [{c: 1}, {d: 1}],
    },
  },
  {
    rule: '/mock2',
    res: {
      j: {
         k: 'XXX'
      },
    },
  },
];

 

M2. 自動檢測環境爲開發環境時啓動Mock.js

// __DEV__ 多是webpack等配置的全局變量
if (__DEV__) {
  require ('./ajaxMock.js');
  require ('./fetchMock.js');
}

 

M3. mock代碼能直接覆蓋global.fetch方法或者XMLHttpRequest構造函數,實現開發無感知

// fetchMock.js
window.fetch = function (url) {
    // 覆蓋默認fetch
}
// ajaxMock.js
class XMLHttpRequest { 
 // ...覆蓋默認XHR
}
window.XMLHttpRequest = XMLHttpRequest;

 

M4.mock配置不影響實際的請求,可無縫切換爲實際請求

mock配置不影響實際的請求,當請求沒有命中mock配置文件中的url時,自動切換爲實際請求,例如json

// fetch
window.fetch = (url, cfg) => {
  if (命中config文件中的url) {
      // 覆蓋默認fetch
  } else {
      return originFetch (url, cfg);
  }
};
// Ajax
const RealXHR = window.XMLHttpRequest;
class XMLHttpRequest {
  open (type, url, bool) {
    if (命中config文件中的url) {
      // 覆蓋Ajax
    } else {
      // 使用系統原有的Ajax
      this.xhr = new RealXHR ();
      this.xhr.open (type, url, bool);
    }
  }
  send (args) {
    if (命中config文件中的url) {
      // 覆蓋Ajax
    } else {
      // 使用系統原有的Ajax
      this.xhr.send (args);
    }
  }
}
window.XMLHttpRequest = XMLHttpRequest;

 

模擬fetch

直接上代碼api

// 保存系統原生的fetch
const originFetch = window.fetch;

// 根據fetch的要求返回的response
const normalize = resp => {
  return {
    ok: true,
    status: 200,
    text () {
      return Promise.resolve (resp);
    },
    json () {
      return Promise.resolve (resp);
    },
  };
};

// 覆蓋fetch
window.fetch = (url, cfg) => {
  // url所對應的JSON對象
  let res;
  // 表示是否config文件中是否有和url對應的配置
  let hit = false;
  // 遍歷配置文件中輸出的數組,檢測並嘗試獲取匹配url的res對象
  fakeApi.forEach (item => {
    let rule = item.rule;
    if (typeof rule === 'string') {
      rule = new RegExp (rule);
    }
    if (rule && rule.test (url)) {
      res = item.res;
      hit = true;
      return false;
    }
  });
  // 若是命中,那麼返回一個Promise,而且傳遞上面和url匹配的JSON對象
  if (hit) {
    return new Promise (resolve => {
      setTimeout (() => {
        resolve (normalize (res));
      }, 1000);
    });
  }
  // 若是沒有命中,那麼使用系統原有的fetch的API,實現無縫切換
  return originFetch (url, cfg);
};

 

 

模擬ajax

直接上代碼數組

// 保存系統原生的XMLHttpRequest對象
const RealXHR = window.XMLHttpRequest;

class XMLHttpRequest {
  constructor () {
    this.url = null;
    this.type = null;
    this.hit = false;
    // 真實的xhr
    this.xhr = null;
  }
  open (type, url, bool) {
    // 遍歷配置文件中輸出的數組,檢測並嘗試獲取匹配url的res對象
    fakeApi.forEach (item => {
      let rule = item.rule;
      if (typeof rule === 'string') {
        rule = new RegExp (rule);
      }
      if (rule && rule.test (url)) {
        this.res = item.res;
        this.hit = true;
        return false;
      }
    });
    // 若是沒有命中,那麼使用系統原有的Ajax的API,實現無縫切換
    if (!this.hit) {
      this.xhr = new RealXHR ();
      this.xhr.open (type, url, bool);
    }
  }
  send (args) {
    // 若是命中,就覆蓋Ajax的API
    if (this.hit && this.onreadystatechange) {
      this.readyState = 4;
      this.status = 200;
      this.responseText = JSON.stringify (this.res);
      this.onreadystatechange ();
    } else {
      // 若是沒有命中,那麼使用系統原有的Ajax的API,實現無縫切換
      this.xhr.send (args);
    }
  }
}
// 覆蓋
window.XMLHttpRequest = XMLHttpRequest;

  

測試

配置文件 dom

export default [
  {
    rule: '/mock',
    res: {
      a: 'data',
      b: [{c: 1}, {d: 1}],
    },
  }
];

 

測試代碼

const xhr = new XMLHttpRequest ();
xhr.onreadystatechange = function () {
  if (xhr.readyState === 4 && xhr.status === 200) {
    console.log (JSON.parse (xhr.responseText));
  }
};
xhr.open ('GET', '/mock');
xhr.send ();

 

測試結果

 

額外擴展

除了上面的功能外,咱們還能作什麼?

  • 加個type類型,區分同一url下的不一樣請求類型,例如get,post

  • 加個布爾值err,表示失敗的請求

上面這兩個功能再作了我以爲就已經很足夠了,固然,若是你還不知足,那你還能夠嘗試:

  • 處理xhr.open的第三個參數:async值,控制同步和異步

  • 處理xhr的progress,load,error,abort等事件監聽

  • 處理fetch返回的response的其餘方法,例如Body.formData()等等

再談mock.js

早在以前我就寫過一篇關於mock.js的文章。這個庫目前在github是13k, 固然我以爲這個庫是很強大的,由於它覆蓋了從名字,地名,文章甚至是圖片資源的mock數據,可是在實際使用中卻多少有那麼一點點「雞肋」的感受,爲何我會有這樣一種感受呢

這是由於它有一套本身的獨立的模板語法,以及API,須要你學習和遵循 

// 模擬JSON數據
Mock.mock({
  "array|1-10": [
    "Hello",
    "Mock.js",
    "!"
  ]
})
// 模擬大段的文章或句子
Random.paragraph( min?, max? )

 

固然mock.js有它本身的好處,例如:

  • 當你須要動態地造大數據量的mock數據的時候很方便,例如mock.js的Random.paragraph的API能很方便的幫你造出來

  • 當你有一些特殊的需求點的時候,例如一個長度寬度變化的圖片的時候,mock.js也能夠很強大的勝任Random.image( size?, background?)

  • 造出來的數據看起來「很漂亮很真實」,單純看徹底發現不了是假的數據

 

但問題在於,我在實際的開發中發現,咱們大多數的數據場景根本就沒這麼複雜

咱們大多數時候須要的僅僅只是:寫一個響應數據的模版,例如一個json文件,而後使得發一個請求過去的時候能在ajax的onreadystatechange或者fetch(url).then中拿到數據就能夠了

若是符合咱們預期的mock的「完美需求」是100%的話

mock.js這個社區應用實現了80%到99%的需求的過程

可是它的使用方式卻額外增長了30% ~ 40%的成本,

由於,咱們大多數時候也許不太須要這麼多的模板和「看起來很漂亮的數據」

這是我寫這個簡易版的mock的實現的緣由


才疏學淺,還多指教,本文完

相關文章
相關標籤/搜索