使用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改成隨機數應該就可以了