开发的过程中在后端接口还没准备好的情况下,前端可以自己mock调试。mock的方式很多,简单的可以自己直接写json赋值,mockjs这种方式是通过拦截原生window.XMLHttpRequest,对业务代码的侵入性更小一点。然而查看mockjs的官网,发现应用文档太少,没有基于框架的引用,故查阅了一番做此整理,并简单解析了一下源码。
整体步骤
- npm安装mockjs
- 新建mock文件,导入mockjs
- main.ts中导入mock文件
具体步骤
- npm install mockjs
- src同级创建mock文件夹
- 新建index.ts,读取其他的mock业务文件
- 新建业务mock文件
- main.ts中通过import()按需导入mock./index.ts,可以控制是否mock拦截
知识点: 标准用法的 import 导入的模块是静态的,会使所有被导入的模块,在加载时就被编译(无法做到按需编译,降低首页加载速度)。有些场景中,你可能希望根据条件导入模块或者按需导入模块,这时你可以使用动态导入代替静态导入。
使用姿势:
// mock/index.ts
// 首先引入Mock
import Mock from 'mockjs'
import { CONF, URL_PRE } from '@/config'
// 设置拦截ajax请求的相应时间
Mock.setup({
timeout: '200-600',
})
interface IService {
path: string
type: string
data: any
}
let configArray: IService[] = []
// 使用webpack的require.context()遍历所有mock文件
const files = require.context('.', true, /\.ts$/)
files.keys().forEach((key) => {
if (key === './index.ts') return
configArray = configArray.concat(files(key).default)
})
// 注册所有的mock服务
configArray.forEach((item) => {
for (const [key, value] of Object.entries(item)) {
const { path, type, data } = value
Mock.mock(`${CONF.apiEndpoint}${URL_PRE}${path}`, type, data)
}
})
// getHost.ts
const hosteList = {
// mock数据
}
const hosteList2 = {
// mock数据
}
const getHostList = {
path: '/mockpath',
type: 'get',
data: {
hostList,
},
}
const getHostList2 = {
path: '/mockpath2',
type: 'get',
data: {
hostList2,
},
}
export default { getHostList, getHostList2 }
// main.ts
// 设置开关,通过动态引入的方式引入mock
const IS_MOCK = true
if(IS_MOCK) {
import('./mock')
}
mockjs原理
- 自定义Mock对象,XHR属性用来拦截的原生window.XMLHttpRequest,_mocked属性用来存储拦截的请求
- 由于拦截了原生XHR,所以在http请求的时候,会走Mock.XHR逻辑
- Mock.XHR,即MockXMLHttpRequest,同样封装了跟原生XHR相同的open(), send(),abort()相同的方法
- open()中,在Mock._mocked中查找是否有匹配的请求,有的话开始拦截,标记拦截请求match;没有的话采用原生 XHR 发送请求。
- send()中,根据拦截标志位match,判断是否采用原生XHR.send();abort()中也同理。
// mock.js
var XHR
if (typeof window !== 'undefined') XHR = require('./mock/xhr')
var Mock = {
Handler: Handler,
Random: Random,
Util: Util,
XHR: XHR,
RE: RE,
toJSONSchema: toJSONSchema,
valid: valid,
heredoc: Util.heredoc,
setup: function(settings) {
return XHR.setup(settings)
},
_mocked: {}
}
// 避免循环依赖
if (XHR) XHR.Mock = Mock
Mock.mock = function(rurl, rtype, template) {
// 拦截 XHR:将自定义的XHR赋值给原生的window.XMLHttpRequest
if (XHR) window.XMLHttpRequest = XHR
// 将每一次的mock请求存到_mocked对象中
Mock._mocked[rurl + (rtype || '')] = {
rurl: rurl,
rtype: rtype,
template: template
}
return Mock
}
module.exports = Mock
// xhr.js
function MockXMLHttpRequest() {
// 初始化 custom 对象,用于存储自定义属性
this.custom = {
events: {},
requestHeaders: {},
responseHeaders: {}
}
}
// 标记当前对象为 MockXMLHttpRequest
MockXMLHttpRequest.prototype.mock = true
// 是否拦截 Ajax 请求
MockXMLHttpRequest.prototype.match = false
// 初始化 Request 相关的属性和方法
Util.extend(MockXMLHttpRequest.prototype, {
// https://xhr.spec.whatwg.org/#the-open()-method
// Sets the request method, request URL, and synchronous flag.
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 请求
// 标记拦截请求
this.match = true
this.readyState = MockXMLHttpRequest.OPENED
}
send: function send(data) {
var that = this
this.custom.options.body = data
// 原生 XHR
if (!this.match) {
this.custom.xhr.send(data)
return
}
// 拦截 XHR
if (this.custom.async) setTimeout(done, this.custom.timeout) // 异步延迟执行done
else done() // 同步直接执行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]
// fix #92 #93 by @qddegtya
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*/ ));
}
}
abort: function abort() {
// 原生 XHR
if (!this.match) {
this.custom.xhr.abort()
return
}
// 拦截 XHR
this.readyState = MockXMLHttpRequest.UNSENT
this.dispatchEvent(new Event('abort', false, false, this))
this.dispatchEvent(new Event('error', false, false, this))
}