使用create-react-app 脚手架创建的项目,并没有集成mock数据的功能,我们在进行前端开发的时候,如果后端服务器暂时没有开发完成,我们就需要使用mock来模拟后端数据,以保证开发的进度.
由于脚手架项目是使用webpack进行打包的,我们就可以利用webpack的devServer的配置,来拦截我们所发送的ajax请求,并返回我们预先设置好的数据
React 脚手架项目,使用mock数据,模拟后端数据
暴露webpack设置 @craco/craco
脚手架创建的项目,默认状态下是不会显示webpack配置选项的,通常我们可以使用 npm run eject 命令来暴露出webpack的配置选项,但是一旦使用这个命令,项目中会多出非常多的文件,而且一旦使用该命令,就无法反悔,如果我们仅仅想要改动一两个地方的配置,显然有点得不偿失,
幸好社区为我们提供了一个非常好用的工具 @craco/craco
链接: https://www.npmjs.com/package/@craco/craco.
它可以让我们在不暴露webpack的情况下进行webpack的配置
使用命令将craco安装到你的项目中
npm install @craco/craco --save
在项目的根目录中创建一个配置文件 craco.config.js
这个是默认的craco的配置文件的位置
当然,如果你想要指定配置文件的位置也是可以的,在package.json中可以单独配置craco的配置文件位置,比如
/* package.json */
{
"cracoConfig": "config/craco-config-with-custom-name.js"
}
一般我们用默认位置就好了
修改package.json文件,改成使用craco来运行项目
将原先的react-scripts改成craco
在craco.config.js文件中输入以下内容
/* craco.config.js */
const apiMocker = require('mocker-api')//可以使用mocker-api库
const path = require('path')
module.exports = {
devServer: {
//如果不使用mocker-api库
//before: require('./mock/index')
//如果使用mocker-api库
before(app)
{
apiMocker(app, path.resolve('./mock/index.js'), {
})
}
}
};
在项目根目录下创建mock文件夹
然后在项目根目录下创建mock文件夹,在这个文件夹里面,我们就可以编写mock数据
通常我会把不同的api请求,放在不同的文件中,比如user相关的写在user.js中,login相关的写在login.js中
书写格式如下:
user.js
module.exports = {
// =====================
// The default GET request.
// https://github.com/jaywcjlove/mocker-api/pull/63
'/api/user': {
id: 1,
username: 'kenny',
sex: 6
},
'GET /api/user/list': [
{
id: 1,
username: 'kenny',
sex: 6
}, {
id: 2,
username: 'kenny',
sex: 6
}
],
'DELETE /api/user/:id': (req, res) =>
{
console.log('---->', req.body)
console.log('---->', req.params.id)
res.send({ status: 'ok', message: '删除成功!' });
}
}
login.js
module.exports = {
'POST /api/login/account': (req, res) =>
{
const { password, username } = req.body;
if (password === '888888' && username === 'admin')
{
return res.json({
status: 'ok',
code: 0,
token: "sdfsdfsdfdsf",
data: {
id: 1,
username: 'kenny',
sex: 6
}
});
} else
{
return res.status(403).json({
status: 'error',
code: 403
});
}
},
}
文件导出一个对象,key值是’POST /api/login/account’这样的格式,前面是 请求方式+空格+path,GET方法可以省略掉请求方式
value值可以直接是一个json 也可以是一个回调函数(req,res)=>{},写法和express的写法一致
注意,一般不在index.js中具体的接口,index.js是聚合导出所有的接口
配置mock/index.js
如果你不想在项目中引入过多的依赖,可以选择不使用mocker-api这个库,自己来实现不不同api的响应
不使用mocker-api
在craco.config.js的配置文件中,本质上为devServer的before属性,定义一个function,进行请求路径的匹配,但是显然,我们不可能在配置文件中编写返回数据的代码
/* craco.config.js */
const path = require('path')
module.exports = {
devServer: {
//如果不使用mocker-api库
before: function (app)
{
//我们不应该在配置文件中编写数据返回
app.get('/api/user/1', (req, res) =>
{
res.json({ id: 123, name: 456 })
});
app.get('/api/login', (req, res) =>
{
res.json({status:1,msg:"xxxxx"})
});
},
};
因此将配置文件craco.config.js改成如下代码
/* craco.config.js */
const path = require('path')
module.exports = {
devServer: {
before: require('./mock/index')
}
};
然后我们在mock/index.js中导出一个function(app),里面包含所有的路径响应
mock/index.js文件内容如下
const fs = require('fs')
const path = require('path')
let mockData={}
//通过递归函数,遍历所有mock文件夹下的文件,汇总所有的路径和响应
/*形成一个如下形式的对象mockData
{
'POST /api/login/account': (req, res) =>{res.json({ id: 123, name: 456 })}),
'/api/user': { id: 1, username: 'kenny', sex: 6 },
'GET /api/user/list': [
{ id: 1, username: 'kenny', sex: 6 },
{ id: 2, username: 'kenny', sex: 6 }
],
'DELETE /api/user/:id': (req, res) =>{res.json({status:1,msg:"xxxxx"})}),
}
key值为请求方法和路径,value值是json数据或者响应回调函数
*/
function readMockDir(dir)
{
let dirs = fs.readdirSync(dir)
dirs.forEach(file =>
{
let _path = path.join(dir, file)
let isDirectory = fs.statSync(_path).isDirectory()
if (isDirectory) {
readMockDir(_path)
}
else
{
Object.assign(mockData,require(_path))
}
})
}
readMockDir(path.join(__dirname))
//导出一个如下形式的函数
//function(app){
// app.get('path',(req,res)=>{})
// app.get('path',(req,res)=>{})
// app.get('path',(req,res)=>{})
// ...
// ...
//}
module.exports = function (app)
{
//遍历上一步代码中生产的mockData对象,将每一个键值对转换成app.get('path',(req,res)=>{})这样的形式
for (let key in mockData)
{
//解析请求方法和路径
let _key = key.replace(/(^\s*)|(\s*$)/g, '')
//console.log(_key);
let _method = 'get'
let _url = _key.replace(/^(get|post|put|delete)\s*/i, function (rs, $1)
{
_method = $1.toLowerCase();
return '';
})
//解析响应是json形式,还是回调形式
//如果是json形式的,就拼装一个(req,res)=>{}回调函数,并设置一个随机的延迟时间,来模拟网络延迟
if (typeof mockData[key] !='function') {
app[_method](_url, (req, res) =>
{
let timeout = (Math.random() * 2800) + 200
//console.log(timeout);
setTimeout(() => {
res.json(mockData[key])
}, timeout);
})
}
//如果响应已经是回调函数的形式了
else
{
app[_method](_url, mockData[key])
}
}
}
这样我们就可以在mock文件夹下任意文件中编写接口,模拟后端的数据
使用mocker-api
当然也可以使用 mocker-api 库,来稍微简化一下上面的代码
首先安装mocker-api
npm install --save-dev mocker-api
在craco.config.js文件中输入以下内容
/* craco.config.js */
const apiMocker = require('mocker-api')//使用mocker-api库
const path = require('path')
module.exports = {
devServer: {
//如果使用mocker-api库
before(app)
{
apiMocker(app, path.resolve('./mock/index.js'), {
})
}
}
};
其实还是配置devServer.before的属性,只是apiMocker()函数帮我们做了解析 请求方法,路径和响应的工作
path.resolve(’./mock/index.js’)这个语句,同样也是导入mock/index.js 文件,只是现在有了mocker-api的帮助,我们只需要导入一个形式如下的对象
{
_proxy: {},
'POST /api/login/account': [Function: POST /api/login/account],
'GET /api/:owner/:repo/raw/:ref/(.*)': [Function: GET /api/:owner/:repo/raw/:ref/(.*)],
'/api/user': { id: 1, username: 'kenny', sex: 6 },
'GET /api/user/list': [
{ id: 1, username: 'kenny', sex: 6 },
{ id: 2, username: 'kenny', sex: 6 }
],
'DELETE /api/user/:id': [Function: DELETE /api/user/:id]
}
同样也是一个json对象,key值代表请求方式和路径,value表示响应,可以是json 也可以是回调函数,其中
_proxy: {},这个对象可以进行一些代理服务器的设置,具体请参考mocker-api的文档
链接:https://www.npmjs.com/package/mocker-api
mock/index.js的功能就是导出一个上面这样形式的对象
mock/index.js
const fs = require('fs')
const path = require('path')
let mockData={}
//通过递归函数,遍历所有mock文件夹下的文件,汇总所有的路径和响应
/*形成一个如下形式的对象mockData
{
'POST /api/login/account': (req, res) =>{res.json({ id: 123, name: 456 })}),
'/api/user': { id: 1, username: 'kenny', sex: 6 },
'GET /api/user/list': [
{ id: 1, username: 'kenny', sex: 6 },
{ id: 2, username: 'kenny', sex: 6 }
],
'DELETE /api/user/:id': (req, res) =>{res.json({status:1,msg:"xxxxx"})}),
}
key值为请求方法和路径,value值是json数据或者响应回调函数
*/
function readMockDir(dir)
{
let dirs = fs.readdirSync(dir)
dirs.forEach(file =>
{
let _path = path.join(dir, file)
let isDirectory = fs.statSync(_path).isDirectory()
if (isDirectory) {
readMockDir(_path)
}
else
{
Object.assign(mockData,require(_path))
}
})
}
readMockDir(path.join(__dirname))
//可以在这里对mocker-api做一些代理服务器等等的配置
const proxy = {
// Priority processing.
// apiMocker(app, path, option)
// This is the option parameter setting for apiMocker
_proxy: {
// proxy: {
// // Turn a path string such as `/user/:name` into a regular expression.
// // https://www.npmjs.com/package/path-to-regexp
// // '/repos/(.*)': 'https://api.github.com/',
// },
// // rewrite target's url path. Object-keys will be used as RegExp to match paths.
// // https://github.com/jaywcjlove/mocker-api/issues/62
// pathRewrite: {
// '^/api/repos/': '/repos/',
// },
// changeHost: true,
// // modify the http-proxy options
// httpProxy: {
// options: {
// ignorePath: true,
// },
// listeners: {
// proxyReq: function (proxyReq, req, res, options)
// {
// console.log('proxyReq');
// },
// },
// },
},
}
//合并对象
Object.assign(proxy, mockData)
//console.log(proxy);
//为总体的响应设置一个延迟来模拟网络延迟
const delay = require('mocker-api/lib/delay');
const noProxy = process.env.NO_PROXY === 'true';
//但是这里的延迟比较尴尬,即使你采用的随机数,也只是在你引入index时产生一次,比如1500ms
//然后你所有的api请求都会是这个固定数值延迟,并不是每个请求单独生产随机延迟
//let delayTime=(Math.random()*1800)+200)
let delayTime=1000
module.exports = (noProxy ? {} : delay(proxy, delayTime);
关于模拟延迟的问题,可以修改mocker-api的源码
node_modules/mocker-api/lib/delay.js 这个文件
直接在 setTimeout 中,把timer改成随机数应该就可以了