zoukankan      html  css  js  c++  java
  • 来,我们手写一个简易版的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的实现的原因


    才疏学浅,还多指教,本文完

  • 相关阅读:
    ReactNative 适合初学的第一个教程demo,找租房
    ReactNative 从环境和第一个demo说起,填坑教程
    WKWebView与JS交互,UIWebView+JavascriptCore和JS交互
    JS中匿名函数$(function(){ })和(function(){})()的区别
    对前端的一个H5项目的所思所想
    使用Swift打造动态库SDK和DemoAPP时所遇到的(Xcode7.3)
    Git 分支合并代码
    Flutter中fluro使用
    flutter_redux框架的使用
    解决React-native init 初始化时 info Installing required CocoaPods dependencies
  • 原文地址:https://www.cnblogs.com/penghuwan/p/11844324.html
Copyright © 2011-2022 走看看