Mock使用及原理分享

249 阅读2分钟

Mock.mock方法

该方法是暴露给开发者的一个方法,用于传递需要mock的url请求和模版数据。

Mock.mock = function(rurl, rtype, template) {
    // Mock.mock(template)
    if (arguments.length === 1) {
        return Handler.gen(rurl)
    }
    // Mock.mock(rurl, template)
    if (arguments.length === 2) {
        template = rtype
        rtype = undefined
    }
    // 拦截 XHR
    if (XHR) window.XMLHttpRequest = XHR
    Mock._mocked[rurl + (rtype || '')] = {
        rurl: rurl,
        rtype: rtype,
        template: template
    }
    return Mock
}

使用方式:

// 模拟后端返回数据
const getMessageList = () => {
  return [
    {
      id: 1,
      type: 'message',
      title: '郑曦月',
      subTitle: '的私信',
      content: '审批请求已发送,请查收',
      time: '今天 12:30:01',
    },
    {
      id: 2,
      type: 'message',
      title: '宁波',
      subTitle: '的回复',
      content:
        '此处 bug 已经修复,如有问题请查阅文档或者继续 github 提 issue~',
      time: '今天 12:30:01',
    }
  ].map((item) => ({
    ...item,
    status: haveReadIds.indexOf(item.id) === -1 ? 0 : 1,
  }));
};

Mock.mock(new RegExp('/api/message/list'), () => {
      return getMessageList();
    });

拦截xhr

源码位置:mock.js/src/mock/xhr/xhr.js

关键实现:

  • 完整地覆盖原生 XHR 的行为
  • 完整地模拟原生 XHR 的行为
  • 在发起请求时,检测是否需要拦截
  • 如果不必拦截,则执行原生 XHR 的行为
  • 如果需要拦截,则执行虚拟 XHR 的行为

关键方法的逻辑:

  1. new 此时尚无法确定是否需要拦截,所以创建原生 XHR 对象是必须的。
  2. open 此时可以取到 URL,可以决定是否进行拦截。
  3. send 此时已经确定了请求方式。

open方法

function find(options) {

    for (var sUrlType in MockXMLHttpRequest.Mock._mocked) {
        //在Mock.mock方法中,把传递的数据模版存在了_mocked对象里
        var item = MockXMLHttpRequest.Mock._mocked[sUrlType]
        if (
            (!item.rurl || match(item.rurl, options.url)) &&
            (!item.rtype || match(item.rtype, options.type.toLowerCase()))
        ) {
            // console.log('[mock]', options.url, '>', item.rurl)
            return item
        }
    }

    function match(expected, actual) {
        if (Util.type(expected) === 'string') {
            return expected === actual
        }
        if (Util.type(expected) === 'regexp') {
            return expected.test(actual)
        }
    }

}

open: function(method, url, async, username, password) {
     
// 只摘取了关键逻辑
   
        // 查找与请求参数匹配的数据模板
        var item = find(this.custom.options)

        // 如果未找到匹配的数据模板,则采用原生 XHR 发送请求。
        if (!item) {
            // 创建原生 XHR 对象,调用原生 open(),监听所有原生事件
            var xhr = createNativeXMLHttpRequest()
            this.custom.xhr = xhr

            // 初始化所有事件,用于监听原生 XHR 对象的事件
            for (var i = 0; i < XHR_EVENTS.length; i++) {
                xhr.addEventListener(XHR_EVENTS[i], handle)
            }

            // xhr.open()
            if (username) xhr.open(method, url, async, username, password)
            else xhr.open(method, url, async)

            // 同步属性 MockXMLHttpRequest => NativeXMLHttpRequest
            for (var j = 0; j < XHR_REQUEST_PROPERTIES.length; j++) {
                try {
                    xhr[XHR_REQUEST_PROPERTIES[j]] = that[XHR_REQUEST_PROPERTIES[j]]
                } catch (e) {}
            }

            return
        }

        // 找到了匹配的数据模板,开始拦截 XHR 请求
        this.match = true
        this.custom.template = item
        this.readyState = MockXMLHttpRequest.OPENED
        this.dispatchEvent(new Event('readystatechange' /*, false, false, this*/ ))
    },

通过open方法,判断是否要开启拦截

send

send: function send(data) {
        var that = this
        this.custom.options.body = data

        // 原生 XHR
        if (!this.match) {
            this.custom.xhr.send(data)
            return
        }

        // 拦截 XHR

        // X-Requested-With header
        this.setRequestHeader('X-Requested-With', 'MockXMLHttpRequest')

       
        this.dispatchEvent(new Event('loadstart' /*, false, false, this*/ ))

        if (this.custom.async) setTimeout(done, this.custom.timeout) // 异步
        else done() // 同步

        function done() {
            that.readyState = MockXMLHttpRequest.HEADERS_RECEIVED
            that.dispatchEvent(new Event('readystatechange' /*, false, false, that*/ ))
            that.readyState = MockXMLHttpRequest.LOADING
            that.dispatchEvent(new Event('readystatechange' /*, false, false, that*/ ))

            that.status = 200
            that.statusText = HTTP_STATUS_CODES[200]

            // 关键的一步:修改response数据为模版数据
            that.response = that.responseText = JSON.stringify(
                convert(that.custom.template, that.custom.options),
                null, 4
            )

            that.readyState = MockXMLHttpRequest.DONE
            that.dispatchEvent(new Event('readystatechange' /*, false, false, that*/ ))
            that.dispatchEvent(new Event('load' /*, false, false, that*/ ));
            that.dispatchEvent(new Event('loadend' /*, false, false, that*/ ));
        }
    },
    
  function convert(item, options) {
  // 如果是模版函数的话,执行该函数得到返回值
    return Util.isFunction(item.template) ?
        item.template(options) : MockXMLHttpRequest.Mock.mock(item.template)
}

通过自定义send方法,拦截了response的数据修改为我们自定义的模版数据,直接修改xhr的状态为done,请求结束。