來,我們手寫一個簡易版的mock.js吧(模擬fetch && Ajax請求)
預期的mock的使用方式
首先我們從使用的角度出發,思考編碼過程
-
M1. 通過配置檔案配置url和response
-
M2. 自動檢測環境為開發環境時啟動Mock.js
-
M3. mock程式碼能直接覆蓋global.fetch方法或者XMLHttpRequest建構函式,實現開發無感知
-
M4. mock配置不影響實際的請求,可無縫切換為實際請求
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時,自動切換為實際請求,例如
// 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
直接上程式碼
// 儲存系統原生的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;
測試
配置檔案
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的實現的原因
才疏學淺,還多指教,本