前端mock数据
一、为什么要模拟数据?
项目开发中,前端工程师需要依赖后端工程师的数据接口以及后端联调环境。但是其实我们也可以根据后端接口文档在接口没有开发完成之前自己mock数据进行调试,让接口消费者脱离接口生产者进行开发。
二、Mock数据常见的解决方案有什么?
在 server 端 mock
在 client 端 mock
-
在代码层硬编码
在负责请求接口的函数里,直接定义一个数据变量,该变量保存了回返的 Mock数据。
这种Mock方法操作比较简单,但缺点也很明显,就是Mock 更改了代码逻辑,和代码耦合性太强。而且并不能模拟真实的网络请求的过程。
-
在前端JS中拦截
典型的解决方案就是Mock.js,通过在业务代码前挂载该JS文件,拦截Ajax请求。这种Mock方式相较于硬编码,虽然实现了Mock 与代码的部分解耦,但无法完全和业务代码解耦(因为必须挂载一个JS并进行 Mock配置)。
虽然提供了大量的Mock API,但是也仍然无法发出真实的网络请求,模拟真实度不够。另外这种方式还存在一些不足,因为是对XHR对象的改写,有些情况下兼容性并不好,比如IE8等低版本浏览器,还有较新的Fetch API也拦截不到。
-
代理软件(Fiddler、Charles抓包)
Fiddler和Charles可以对网络请求进行拦截将其替换为我们需要的Mock 数据,这也不失为一种Mock方式。其优点主要是真实性强,但这种方式操作步骤比较繁琐,不方便统一配置,Mock成本较高。
-
Mock-Server
最合适的方案无外乎搭建独立的 Mock-Server,开发的前期阶段,所有的接口都会指向该 Mock-Server。因为可能存在跨域的情况,所以一般都需要在开发环境搭配一套接口代理做搭配。这种方案对业务代码完全不具有侵入性,并且通用性强。
缺点也很明显,成本高(还需要另起一个Mock-Server服务,并对其进行管理)。
-
RAP
......
三、Mock数据实例
1. MockJS
数据生成器有很多,比较出名的有faker、chance、mockjs等,其中最为强大的非faker莫属,不但拥有几乎全部常用的数据格式,而且还有中英德法等多种语言的数据。但是在实际测试中发现,faker对中文数据的支持还是以西方文字为基础,并不能很好的模拟中文。并且mockjs使用了位于国内的随机图片提供商。
原理:Mock.js 通过覆盖和模拟原生 XMLHttpRequest 的行为来拦截 Ajax 请求“转发”到本地文件,所谓转发,其实就是读取本地 mock文件,并以json或者script等格式返回给浏览器(还需要补充)。
mockjs能做的事情是拦截Ajax请求,可以返回各种随机的数据。
Webpack dev.config.js
:
plugins:[
new webpack.DefinePlugin({
MOCK: true
})
]
DefinePlugin
插件允许创建一个在编译时可以配置的全局变量,
使用功能标记来「启用/禁用」「生产/开发」构建中的功能。
入口文件:
if (MOCK) {
require('mock/mock');
}
Project mock/mock.js
:
import Mock from 'mockjs';
Mock.mock('/api/user', {
'name': '@cname',
'intro': '@word(20)'
});
Mock.js的语法规范:
根据数据模板生成模拟数据
Mock.mock( rurl?, rtype?, template|function( options ) )
-
数据模板定义规范
数据模板中的每个属性由 3 部分构成:属性名、生成规则、属性值
'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
-
数据占位符定义规范
@
示例:
'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': [
'已注册',
'已开户',
'已入金',
'已注销'
],
'guid': '@guid',
'first': '@cfirst()',
'last': '@clast()',
'name': '@cname',
// 随机生成一个18位身份证
'id': '@id',
'title': '@ctitle(3,10)',
'paragraph': '@cparagraph(2,5)',
'image': "@image('200x100', '#4A7BF7', 'img', 'png', 'Tiger')",
// 随机生成一个6位的邮编
'zip': '@zip()',
// 随机生成一个中国大区
'region': '@region()',
// 随机生成一个(中国)省(或直辖市、自治区、特别行政区
'province': '@province()',
// 随机生成一个(中国)市,prefix指示是否生成所属的省
'city': '@city(true)',
// 随机生成一个(中国)县,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',
// 随机生成一个域名
'domain': '@domain()',
// 随机生成一个顶级域名
'tld': '@tld()',
// 指定邮件的地址域名
'email': '@email("gmail.com")',
'color': '@color()',
'ip': '@ip',
'regexp': /[a-z][A-Z][0-9]/,
}]
我们希望mock该有的能力:
-
与线上环境一致的接口地址,每次构建前端代码时不需要修改调用接口的代码
-
所改即所得,具有热更新的能力,每次增加/修改mock 接口时不需要重启mock服务,更不用重启前端构建服务
-
能配合构建工具
-
mock数据可以由工具生成不需要自己手动写
-
能模拟POST、GET等请求
-
基于数据模板生成数据
所以我们有了第二种方案:
2. json-server + mockjs + nodemon
json-server主要是搭建一台json服务器,支持CORS和JSONP跨域请求。支持 GET、POST、PUT、PATCH和DELETE方法,更提供了一系列的查询方法,如 limit、order等。
CLI usage
➜ json-server -h
json-server [options] <source>
选项:
--config, -c 指定config文件 [默认值: "json-server.json"]
--port, -p 设置端口号 [默认值: 3000]
--host, -H 设置主机 [默认值: "0.0.0.0"]
--watch, -w 监控文件 [布尔]
--routes, -r 指定路由文件
--middlewares, -m ---- [数组]
--static, -s 设置静态文件
--read-only, --ro 只允许GET请求 [布尔]
--no-cors, --nc 禁止跨域资源共享 [布尔]
--no-gzip, --ng 禁止GZIP [布尔]
--snapshots, -S 设置快照目录 [默认值: "."]
--delay, -d 设置反馈延时(ms)
--id, -i 设置数据的id属性(e.g. _id) [默认值: "id"]
--foreignKeySuffix, --fks Set foreign key suffix (e.g. _id as in post_id)
--quiet, -q 不输出日志信息 [布尔]
--help, -h 显示帮助信息 [布尔]
--version, -v 显示版本号 [布尔]
json-server // json-server服务
nodemon // 修改配置无需重启服务
mockjs // 批量生成数据
具体的实现方案:
package.json
"scripts": {
...
"mockdev": "nodemon mock/server.js & npm start"
}
目录结构
|--mock
|--config.js 配置文件
|--db.js 数据文件
|--routes.js 路由映射
|--server.js 服务文件
config.js -- 配置端口等
module.exports = {
SERVER:"127.0.0.1",
//定义端口号
PORT: 3003,
//定义数据文件
DB_FILE:"./db.js"
};
db.js -- 批量生产数据文件
let Mock = require('mockjs');
let Random = Mock.Random;
module.exports = function() {
var data = {
<!--实际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));
// 设置增加一个响应头信息“从server到前端”
server.use((req, res, next) => {
res.header('X-Hello', 'World');
next();
})
// 数据发送到前端之前包一层
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"
}
}