我们知道ant-design-pro引入了umi进行数据模拟,但是这依然需要后端的支持。但是在某些应用场景下,尤其是在我们没有后台服务器的情况下,想要进行演示的话,就要考虑进行纯粹的前端模拟,为此我们引入mockjs。
本文的所有代码已托管。
我用mockjs是为了进行gh-pages的托管,效果可以点此查看
代理umi的mock数据
在umi 里约定mock
文件夹下的文件或者 page(s)
文件夹下的_mock
文件即 mock 文件,在此我们希望在非侵入式的情况下优雅的使用mockjs代理这些文件。我们知道计算机科学领域的任何问题都可以通过增加一个间接的中间层来解决
,因此初步的想法是增加一个代理动态的导入umi约定的mock模块,然后解析并改装成mockjs的语法。
实现require.context
在前端工程自动化中,我们经常用到webpack
的require.context
进行模块的自动化加载,这能够满足我们导入mock模块的需求,但是require.context是webpack的函数,属于开发依赖包,运行时不存在此函数,需要我们自行实现,代码如下:
if (typeof require.context === 'undefined') {
require.context = (base = '.', scanSubDirectories = false, regularExpression = /\.[jt]s$/) => {
const files = {};
function readDirectory(directory) {
fs.readdirSync(directory).forEach(file => {
const fullPath = resolve(directory, file);
if (fs.statSync(fullPath).isDirectory()) {
if (scanSubDirectories) readDirectory(fullPath);
return;
}
if (!regularExpression.test(fullPath)) return;
files[fullPath] = true;
});
}
readDirectory(resolve(__dirname, base));
function Module(file) {
// eslint-disable-next-line global-require,import/no-dynamic-require
return require(file);
}
Module.keys = () => Object.keys(files);
return Module;
};
}
动态加载约定的mock模块
这边就是干两件事
- 加载mock文件夹下的模块,加载pages下文件名为_mock的模块
- 将加载的mock对象合并为一个对象
let mocks = {};
const modulesFiles = [
require.context('./', true),
require.context('../src/pages/', true, /_mock\.[jt]s$/),
];
const tmp = [];
modulesFiles.forEach(x => {
// eslint-disable-next-line array-callback-return
x.keys().reduce((modules, modulePath) => {
const m = modulePath.replace(/\.[jt]s/g, '');
if (!tmp.includes(m)) {
const value = x(modulePath);
if (value.default !== undefined) {
mocks = Object.assign(mocks, value.default);
}
tmp.push(m);
}
}, {});
});
转换mock为mockjs
- 如果mock接口返回的是非函数,则认为是接口返回的数据,直接交给mockjs处理
- 如果mock接口为函数,则进行函数调用之后交给mockjs
function mockXHR() {
Mock.XHR.prototype.proxy_send = Mock.XHR.prototype.send;
Mock.XHR.prototype.send = () => {
if (this.custom.xhr) {
this.custom.xhr.withCredentials = this.withCredentials || false;
if (this.responseType) {
this.custom.xhr.responseType = this.responseType;
}
}
// eslint-disable-next-line prefer-rest-params
this.proxy_send(...arguments);
};
function XHR2ExpressReqWrap(respond) {
return options => {
let result = null;
if (respond instanceof Function) {
const { body = '{}', method, url } = options;
// https://expressjs.com/en/4x/api.html#req
result = respond(
{
url: options.url,
method,
body: JSON.parse(body),
query: param2Obj(url),
},
undefined,
options.url,
{
body: JSON.parse(body),
},
);
} else {
result = respond;
}
return Mock.mock(result);
};
}
Object.keys(mocks).forEach(i => {
let url = i;
let method = 'GET';
const res = /^(GET|POST|DELETE|PUT) /.exec(i.toUpperCase());
if (res && res.length === 2) {
// eslint-disable-next-line prefer-destructuring
method = res[1];
url = url.substring(method.length + 1);
}
Mock.mock(new RegExp(url), method, XHR2ExpressReqWrap(mocks[i]));
});
}
// 调用转换器
mockXHR();
增加mock fetch的支持
运行之后我们发现实际上浏览器还是向后端发出了请求, 并没有被拦截。发现原来mockjs仅仅支持ajax的请求拦截,而umi使用的是fetch,并不被mockjs所支持,在此我们需求增加另外一层代理,使得mockjs支持fetch。为此,在github上找到了一个叫做mockjs-fetch
的库,下载下来稍加改造即可。
结语
至此我们可以在不改变ant-design-pro
的框架下,使用mockjs进行请求拦截,并且随时可拔插。