来,我们手写一个简易版的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的实现的原因
才疏学浅,还多指教,本文完