在前后端分离的趋势下,前端开发逐步减少对后端的依赖,甚至可以不与后端进行联调,独立交付需求。在这样的背景下,首先要解决的就是接口数据的Mock,根据服务端约定好的接口进行数据Mock,同时修改数据来满足不同的开发场景。
一、请求流程介绍
首先来分析下开发环境下请求的整个流程:
发送请求流程:
1. 操作页面时首先会经过编译后的源码,按照源码的逻辑进行请求封装,再发送请求
2. 请求通过浏览器向目标地址发送请求,开发环境下目标地址为编译工具提供的前端服务器地址
3. 编译工具接收到请求后,根据配置项对请求处理,处理完成后向后端服务器发送请求
响应请求流程:
1. 后端服务器接收请求后,匹配到对应的路由进行逻辑处理,并将结果返回
2. 编译工具拿到结果,根据配置项内容进行处理,向浏览器返回处理后的结果
3. 浏览器接收数据后,按照源码逻辑处理数据,将新的数据呈现在页面上
二、Mock解决方案
按照请求在本地和服务器两端控制,我们将Mock方式分为本地Mock和服务器Mock:
根据上述请求的整个流程,前端Mock数据的解决方案可以大致分为以下几种:
1. 本地Mock
1.1 逻辑嵌入
1.2 源码请求拦截
1.3 浏览器请求拦截
1.4 代理封装
2. 服务器Mock
2.1 服务化
2.2 代理拦截
2.3 服务拦截
我们以渲染一个列表为例,使用Axios获取接口数据:
// userlist.js 文件
import axios from 'axios'
const getUserList = async () => {
const users = await axios.get('/user/list')
return users.data.responseData
}
三、Mock方案实践
一. 本地Mock
方式一:逻辑嵌入
最简单一个方式就是将数据直接返回,不发送请求。
// mock.js
export default users = {
status: 200,
data: {
responseData: [{
name: 'user1'
}],
err: null
}
}
// userlist.js 文件
import axios from 'axios'
import usersData from 'mock.js'
const getUserList = async () => {
// const users = await axios.get('/user/list')
const users = usersData
return users.data.responseData
}
结论:
优点: 前端控制接口数据,不必通过后端增删改来修改返回数据;
缺点: 这种方式侵入到了代码中,开发和后期维护肯定是极其不友好的;
方式二:源码请求拦截
使用中间件,统一在中间件做处理,axios提供了拦截器,可以在响应的拦截器中做统一的处理。
(不只是axios,其他的请求工具也都提供了拦截器的功能,这里主要说明解决方案)
// axiosConfig.js配置文件
import usersData from 'mock.js'
import axios from 'axios'
axios.interceptors.response.use((response) => {
if (response.request.url === '/user/list') {
return usersData;
}
return response
}, (error) => {
return Promise.reject(error);
});
export default axios
// userlist.js 文件
import axios from 'axiosConfig'
const getUserList = async () => {
const users = await axios.get('/user/list')
return users.data.responseData
}
结论:
优点: 这种方式与上一个相比,统一在一个文件内管理,减少了对业务代码的侵入;
缺点: 同样的还是有代码的侵入;
方式三:浏览器请求拦截
拦截请求,请求直接在浏览器里返回结果,使用Service Worker对请求进行出来,返回自定义结果。比如 Mock Service Work工具
import { rest, setupWorker } from 'msw'
import userData from 'mock.js'
const handlers = [
rest.get('/user', () => {
return res(
ctx.status(200),
ctx.json(userData)
)
})
]
const worker = setupWorker(...handlers)
if (process.env.NODE_ENV === 'development') {
worker.start()
}
结论:
优点:统一在一个文件内管理,较少对代码的侵入;
缺点:在项目内还是会有侵入,数据修改不灵活;
方式四:代理封装
使用打包工具的本地服务功能转发请求,在开发环境下,都会起一个服务,用于本地访问查看页面,基于这个流程,可以在服务上做一些处理。
(以webpack工具为例,在代理配置中添加路由,并在该路由下修改返回对象。)
// webpack.dev.js
module.exports = {
//...
devServer: {
proxy: {
'/user': {
target: 'http://localhost:3000',
bypass: function (req, res, proxyOptions) {
// 需要转换 mock.js 为 json文件,webpack在node环境下不支持import方式
let jsonPath = path.resolve(__dirname, 'mock.json')
res.status(200).send( JSON.parse( fs.readFileSync(jsonPath) ) )
}
}
}
}
};
以webpack工具为例,在代理配置中添加路由,并在该路由下修改返回对象。
结论:
优点:使用代理转发的方式,无代码侵入,一种全新的思路,是质的提升;
缺点:需要在本地生成多个json文件,统一个接口,增删改查都需要配置不同的json文件;
二. 服务器Mock
继上面本地转发的方案后,可以将json文件抽离到项目之外,开启一个服务器将json暴露出去,由本地转发替换为转发到服务器上。
基于这样的方案,可以使用数据库进行存储代替json文件,越来越多的Mock平台出现了,比如 大搜车的easymock、阿里的 rap2、去哪儿网的 Yapi平台等等,Yapi接口管理工具不只是前端工具,也集成了测试方案,是很优秀的工具。
方式一:服务化
打包工具转发,通过配置代理地址,直接请求到平台的接口。
首先在平台上注册账户并添加自己的接口,然后在代理中配置地址。
(以webpack工具为例,在代理配置中添加路由,配置该路由的转发地址。)
// webpack.dev.js
module.exports = {
//...
devServer: {
proxy: {
'/user': {
target: 'http://xxx.xxx.com/xxx' // 根据平台地址填写
}
}
}
};
结论:
优点:接口数据可在平台实时修改,项目无其他非相关的代码;
缺点:每次接口修改,都需要重启服务,修改成本较高;
方式二:代理拦截
使用代理服务器进行转发,脱离对框架的依赖。在打包工具访问平台的网络链路流程中,添加一个代理服务器,控制代理服务器从而控制API转发流程。
(以nginx工具为例,配置不同路由对应的服务器。)
# nginx.conf
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
server {
listen 8081;
server_name 127.0.0.1;
root D:/worksapce/project;
location /user {
# 根据平台地址填写
proxy_pass http://xxx.xxx.com/xxx;
}
}
}
// webpack.dev.js
module.exports = {
//...
devServer: {
proxy: {
'/user': {
target: 'http://127.0.0.1:8081'
}
}
}
};
结论:
优点:接口在代理服务器控制,可随时切换Mock平台,API地址,解决跨域等问题,不必重启打包工具;
缺点:需要安装学习nginx工具,在临时处理问题等一些特殊场景下成本较高;
方式三:服务拦截
使用后端服务转发,后端服务不仅可以开发前端接口,返回需要的数据外,还可以进行路由转发,返回对应服务器的接口数据。
// proxy.js 文件
const http = require('http')
const httpProxy = require('http-proxy')
// 创建一个代理服务
const proxy = httpProxy.createProxyServer();
//创建http服务器并监听8888端口
let server = http.createServer(function (req, res) {
let url = req.url
if (url.indexOf('/user') >-1) {
//将用户的请求转发到本地9999端口上
proxy.web(req, res, () => {
return {
target: 'http://xxx.xxx.com/xxx;', // 根据平台地址填写
}
});
//监听代理服务错误
proxy.on('error', function (err) {
console.log(err);
});
}
});
server.listen(8081, '0.0.0.0');
// webpack.dev.js
module.exports = {
//...
devServer: {
proxy: {
'/user': {
target: 'http://127.0.0.1:8081'
}
}
}
};
结论:
优点:快速搭建代理服务,前端友好的Node环境可减低开发难度,代理服务重启成本低;
缺点:需要多启动一个代理服务进行转发;
四、Mock方案总结
本篇文章主要是对Mock数据方案的归纳整理,列举了简要的操作步骤,详细步骤可自行查找相关资料。
结合上面的解决方案,可以根据场景的需要选择不同的方案:
1. 临时页面修改,可通过代码侵入的方式快速解决;
2. 对应新的项目,可以使用代理服务的方式规范开发;
3. 处理多个项目时,可以通过nginx配置多个端口来降低服务重启的成本。