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 的行为
关键方法的逻辑:
- new 此时尚无法确定是否需要拦截,所以创建原生 XHR 对象是必须的。
- open 此时可以取到 URL,可以决定是否进行拦截。
- 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,请求结束。