一纵潦、為什么要模擬數(shù)據(jù)币他?
項(xiàng)目開發(fā)中尔当,前端工程師需要依賴后端工程師的數(shù)據(jù)接口以及后端聯(lián)調(diào)環(huán)境莲祸。但是其實(shí)我們也可以根據(jù)后端接口文檔在接口沒有開發(fā)完成之前自己mock數(shù)據(jù)進(jìn)行調(diào)試蹂安,讓接口消費(fèi)者脫離接口生產(chǎn)者進(jìn)行開發(fā)椭迎。
二、Mock數(shù)據(jù)常見的解決方案有什么田盈?
在 server 端 mock
在 client 端 mock
-
在代碼層硬編碼
在負(fù)責(zé)請(qǐng)求接口的函數(shù)里畜号,直接定義一個(gè)數(shù)據(jù)變量,該變量保存了回返的 Mock數(shù)據(jù)允瞧。
這種Mock方法操作比較簡(jiǎn)單简软,但缺點(diǎn)也很明顯,就是Mock 更改了代碼邏輯述暂,和代碼耦合性太強(qiáng)痹升。而且并不能模擬真實(shí)的網(wǎng)絡(luò)請(qǐng)求的過程。
-
在前端JS中攔截
典型的解決方案就是Mock.js畦韭,通過在業(yè)務(wù)代碼前掛載該JS文件疼蛾,攔截Ajax請(qǐng)求。這種Mock方式相較于硬編碼艺配,雖然實(shí)現(xiàn)了Mock 與代碼的部分解耦察郁,但無法完全和業(yè)務(wù)代碼解耦(因?yàn)楸仨殥燧d一個(gè)JS并進(jìn)行 Mock配置)。
雖然提供了大量的Mock API转唉,但是也仍然無法發(fā)出真實(shí)的網(wǎng)絡(luò)請(qǐng)求皮钠,模擬真實(shí)度不夠。另外這種方式還存在一些不足赠法,因?yàn)槭菍?duì)XHR對(duì)象的改寫麦轰,有些情況下兼容性并不好,比如IE8等低版本瀏覽器砖织,還有較新的Fetch API也攔截不到款侵。
-
代理軟件(Fiddler、Charles抓包)
Fiddler和Charles可以對(duì)網(wǎng)絡(luò)請(qǐng)求進(jìn)行攔截將其替換為我們需要的Mock 數(shù)據(jù)镶苞,這也不失為一種Mock方式喳坠。其優(yōu)點(diǎn)主要是真實(shí)性強(qiáng),但這種方式操作步驟比較繁瑣茂蚓,不方便統(tǒng)一配置壕鹉,Mock成本較高剃幌。
-
Mock-Server
最合適的方案無外乎搭建獨(dú)立的 Mock-Server,開發(fā)的前期階段晾浴,所有的接口都會(huì)指向該 Mock-Server负乡。因?yàn)榭赡艽嬖诳缬虻那闆r,所以一般都需要在開發(fā)環(huán)境搭配一套接口代理做搭配脊凰。這種方案對(duì)業(yè)務(wù)代碼完全不具有侵入性抖棘,并且通用性強(qiáng)。
缺點(diǎn)也很明顯狸涌,成本高(還需要另起一個(gè)Mock-Server服務(wù)切省,并對(duì)其進(jìn)行管理)。
-
RAP
......
三帕胆、Mock數(shù)據(jù)實(shí)例
1. MockJS
數(shù)據(jù)生成器有很多朝捆,比較出名的有faker、chance懒豹、mockjs等芙盘,其中最為強(qiáng)大的非faker莫屬,不但擁有幾乎全部常用的數(shù)據(jù)格式脸秽,而且還有中英德法等多種語言的數(shù)據(jù)儒老。但是在實(shí)際測(cè)試中發(fā)現(xiàn),faker對(duì)中文數(shù)據(jù)的支持還是以西方文字為基礎(chǔ)记餐,并不能很好的模擬中文驮樊。并且mockjs使用了位于國(guó)內(nèi)的隨機(jī)圖片提供商。
原理:Mock.js 通過覆蓋和模擬原生 XMLHttpRequest 的行為來攔截 Ajax 請(qǐng)求“轉(zhuǎn)發(fā)”到本地文件剥扣,所謂轉(zhuǎn)發(fā)巩剖,其實(shí)就是讀取本地 mock文件,并以json或者script等格式返回給瀏覽器(還需要補(bǔ)充)钠怯。
mockjs能做的事情是攔截Ajax請(qǐng)求佳魔,可以返回各種隨機(jī)的數(shù)據(jù)。
Webpack dev.config.js
:
plugins:[
new webpack.DefinePlugin({
MOCK: true
})
]
DefinePlugin
插件允許創(chuàng)建一個(gè)在編譯時(shí)可以配置的全局變量晦炊,
使用功能標(biāo)記來「啟用/禁用」「生產(chǎn)/開發(fā)」構(gòu)建中的功能鞠鲜。
入口文件:
if (MOCK) {
require('mock/mock');
}
Project mock/mock.js
:
import Mock from 'mockjs';
Mock.mock('/api/user', {
'name': '@cname',
'intro': '@word(20)'
});
Mock.js的語法規(guī)范:
根據(jù)數(shù)據(jù)模板生成模擬數(shù)據(jù)
Mock.mock( rurl?, rtype?, template|function( options ) )
-
數(shù)據(jù)模板定義規(guī)范
數(shù)據(jù)模板中的每個(gè)屬性由 3 部分構(gòu)成:屬性名、生成規(guī)則断国、屬性值
'name|rule': value
'name|min-max': value
'name|count': value
'name|min-max.dmin-dmax': value
'name|min-max.dcount': value
'name|count.dmin-dmax': value
'name|count.dcount': value
'name|+step': value
數(shù)據(jù)占位符定義規(guī)范
@
示例:
'list|1-10': [{
'order_id|+1': 1,
'user_id|100-200': 1,
'is_deposit|1': true,
'city|2-4': {
"110000": "北京市",
"120000": "天津市",
"130000": "河北省",
"140000": "山西省"
},
'status|1': [
'已注冊(cè)',
'已開戶',
'已入金',
'已注銷'
],
'guid': '@guid',
'first': '@cfirst()',
'last': '@clast()',
'name': '@cname',
// 隨機(jī)生成一個(gè)18位身份證
'id': '@id',
'title': '@ctitle(3,10)',
'paragraph': '@cparagraph(2,5)',
'image': "@image('200x100', '#4A7BF7', 'img', 'png', 'Tiger')",
// 隨機(jī)生成一個(gè)6位的郵編
'zip': '@zip()',
// 隨機(jī)生成一個(gè)中國(guó)大區(qū)
'region': '@region()',
// 隨機(jī)生成一個(gè)(中國(guó))氏湍贰(或直轄市、自治區(qū)稳衬、特別行政區(qū)
'province': '@province()',
// 隨機(jī)生成一個(gè)(中國(guó))市霞捡,prefix指示是否生成所屬的省
'city': '@city(true)',
// 隨機(jī)生成一個(gè)(中國(guó))縣,prefix指示是否生成所屬的省薄疚、市
'address': '@county(true)',
'date': '@date("yyyy-MM-dd")',
'datetime': '@datetime("yyyy-MM-dd HH:mm:ss")',
'time': '@time("HH:mm:ss")',
'sentence': '@csentence(2, 5)',
'url': '@url',
// 隨機(jī)生成一個(gè)域名
'domain': '@domain()',
// 隨機(jī)生成一個(gè)頂級(jí)域名
'tld': '@tld()',
// 指定郵件的地址域名
'email': '@email("gmail.com")',
'color': '@color()',
'ip': '@ip',
'regexp': /[a-z][A-Z][0-9]/,
}]
我們希望mock該有的能力:
與線上環(huán)境一致的接口地址碧信,每次構(gòu)建前端代碼時(shí)不需要修改調(diào)用接口的代碼
所改即所得赊琳,具有熱更新的能力,每次增加/修改mock 接口時(shí)不需要重啟mock服務(wù)砰碴,更不用重啟前端構(gòu)建服務(wù)
能配合構(gòu)建工具
mock數(shù)據(jù)可以由工具生成不需要自己手動(dòng)寫
能模擬POST躏筏、GET等請(qǐng)求
基于數(shù)據(jù)模板生成數(shù)據(jù)
所以我們有了第二種方案:
2. json-server + mockjs + nodemon
json-server主要是搭建一臺(tái)json服務(wù)器,支持CORS和JSONP跨域請(qǐng)求呈枉。支持 GET趁尼、POST、PUT猖辫、PATCH和DELETE方法酥泞,更提供了一系列的查詢方法,如 limit住册、order等婶博。
CLI usage
? json-server -h
json-server [options] <source>
選項(xiàng):
--config, -c 指定config文件 [默認(rèn)值: "json-server.json"]
--port, -p 設(shè)置端口號(hào) [默認(rèn)值: 3000]
--host, -H 設(shè)置主機(jī) [默認(rèn)值: "0.0.0.0"]
--watch, -w 監(jiān)控文件 [布爾]
--routes, -r 指定路由文件
--middlewares, -m ---- [數(shù)組]
--static, -s 設(shè)置靜態(tài)文件
--read-only, --ro 只允許GET請(qǐng)求 [布爾]
--no-cors, --nc 禁止跨域資源共享 [布爾]
--no-gzip, --ng 禁止GZIP [布爾]
--snapshots, -S 設(shè)置快照目錄 [默認(rèn)值: "."]
--delay, -d 設(shè)置反饋延時(shí)(ms)
--id, -i 設(shè)置數(shù)據(jù)的id屬性(e.g. _id) [默認(rèn)值: "id"]
--foreignKeySuffix, --fks Set foreign key suffix (e.g. _id as in post_id)
--quiet, -q 不輸出日志信息 [布爾]
--help, -h 顯示幫助信息 [布爾]
--version, -v 顯示版本號(hào) [布爾]
json-server // json-server服務(wù)
nodemon // 修改配置無需重啟服務(wù)
mockjs // 批量生成數(shù)據(jù)
具體的實(shí)現(xiàn)方案:
package.json
"scripts": {
...
"mockdev": "nodemon mock/server.js & npm start"
}
目錄結(jié)構(gòu)
|--mock
|--config.js 配置文件
|--db.js 數(shù)據(jù)文件
|--routes.js 路由映射
|--server.js 服務(wù)文件
config.js -- 配置端口等
module.exports = {
SERVER:"127.0.0.1",
//定義端口號(hào)
PORT: 3003,
//定義數(shù)據(jù)文件
DB_FILE:"./db.js"
};
db.js -- 批量生產(chǎn)數(shù)據(jù)文件
let Mock = require('mockjs');
let Random = Mock.Random;
module.exports = function() {
var data = {
<!--實(shí)際mock的接口-->
};
return data;
}
server.js
const config = require('./config');
const jsonServer = require('json-server');
const rules = require('./routes');
const dbfile = require(config.DB_FILE);
const ip = config.SERVER;
const port = config.PORT;
const db_file = config.DB_FILE;
// Express server
const server = jsonServer.create();
// JSON Server router
const router = jsonServer.router(dbfile());
// 中間件
const middlewares = jsonServer.defaults();
server.use(jsonServer.bodyParser);
server.use(middlewares);
// 重寫路由
server.use(jsonServer.rewriter(rules));
// 設(shè)置增加一個(gè)響應(yīng)頭信息“從server到前端”
server.use((req, res, next) => {
res.header('X-Hello', 'World');
next();
})
// 數(shù)據(jù)發(fā)送到前端之前包一層
router.render = (req, res) => {
res.jsonp({
code: 0,
body: res.locals.data
})
}
server.use(jsonServer.rewriter(rules));
server.use(router);
server.listen({
host: ip,
port: port,
}, function() {
console.log(JSON.stringify(jsonServer));
console.log(`JSON Server is running in http://${ip}:${port}`);
});
routes.js -- 路由映射
module.exports= {
"/api/": "/",
"/:id": "/news/:id",
"/news/:id/show": "/news/:id",
"/topics/:id/show": "/news/:id"
}
webpack.dev.cong.js -- 代理
devServer: {
...
proxy: {
"/api/*": "http://localhost:3003"
}
}