前言: 在实际的开发过程当中,前、后端仍然存在过程式子的耦合,即前端人员过分的依赖服务端所提供的数据。这不只会耽误前端开发的进度,也容易在联调过程当中致使一些没必要要的麻烦。所谓,先后端彻底分离,就是要提供一种机制让前端开发人员再也不依赖服务端的数据,而是接口。
在实际的项目中,前端开发人员每每会遇到如下的问题而致使前端进度的缓慢:javascript
致使这种现象的根本缘由就是前端过分依赖服务端,为了能解开这种高密度的耦合,咱们首先是要创建公约制接口,而后按照接口进行离线开发。这样,咱们能够先按照预先的接口进行模拟,待到双方条件具有时,按照接口进行联调测试,就方便许多。前端
要想让前端隔离于服务端,就要在前端去作数据模拟——Mock。数据模拟的方式有不少,大体能够分为三类:vue
第一种方式是不值得提倡的,由于这样作将破坏ajax原有功能,也会造成许多垃圾代码,致使后期维护难度大,例如:java
Axios.get('/api/users') .then(/* ... */) .catch(err => { const data = { /* ...*/ }; /* 对data的处理 */ })
catch真正应该构建的不该该是数据模拟,而是对页面上交互流程的处理,例如在数据请求失败时,给予用户相应的提示。webpack
对于第三种方式,也是比较容易实现的,在webpack中配置一下代理便可,而后请求时,将原有接口改成代理连接便可。如今,各类第三方MockServer已经很是成熟,但却依赖缓落环境,不适合离线开发的场景。若是本身有搭建服务器的能力,也能够搭建本地MockServer,但其使用成本没有第二种来的方便。ios
最后来讲说第二种方式,这也是我我的最倾向的一种方式。在vue体系中,最多见的就是使用MockJS来拦截Axios的请求。但在实际使用过程当中,语法上仍然是比较晦涩的,咱们来看看使用原生的MockJS是如何构建拦截服务的:git
import Mock from 'mockjs'; // 拦截请求-动态路由 get /api/user/:id Mock.mock(/\/api\/user\/[0-9a-zA-Z]{8}-[0-9a-zA-Z]{4}-[0-9a-zA-Z]{4}-[0-9a-zA-Z]{4}-[0-9a-zA-Z]{12}/, 'GET', options => { return { /* 返回的模拟数据 */}; }); // 拦截请求-参数化路由 get /api/users? Mock.mock(/\/api\/user+(\?{0,1}(([A-Za-z0-9-~]+\={0,1})([A-Za-z0-9-~]*)\&{0,1})*)$/, GET, options => { return { /* 返回的模拟数据 */}; });
看看这样的代码,是否是发现特别的晦涩难懂,并且还不太容易维护,开发人员要手写一段更复杂的mock也会很容易写错。架构的本质是让复杂的事情变得简单,所以,在此基础上,咱们须要多Mock进行改进。github
我一直在思考一个问题,为何Mock的写法不能和AJAX的写法一一对应呢?AJAX能够采用RESTFUL的方式来书写,Mock也应当按照这种方式来书写。构成这一想法的启蒙框架就是EXPRESS,EXPRESS提供了RESTFUL方式的路由,方便开发者将项目按照路由地址进行模块化。所以,咱们就须要有以下的指望:web
// AJAX请求 Axios.get('/api/user/10000') .then(res => { /* 处理 res.data */}); // Mock模拟 Mock.get('/api/user/:id', id => { return { /* 返回数据 data */ }; })
看看这样的指望,AJAX发送请求后,能拿到Mock返回的数据data。于此同时,Mock模拟的数据能拦截全部诸如/api/user/:id
的请求,这里占位符拿到的数据为id = 10000
,而且在Mock的回调函数中咱们还能够作动态处理。ajax
经过编码,咱们对Mock进行相应的扩展,其最终功能以下:
// 按照数据模板返回模拟数据 Mock.mock({ /* MockJS template */ }); // 语义化RESTFUL请求模拟 Mock.get('/api/users', () => {}); Mock.post('/api/user', () => {}); // 动态路由模拟 Mock.get('/api/user/:id', id => {}); // get请求参数拦截 Mock.get('/api/user/:id', (id, params) => {}); // post请求参数拦截 Mock.post('/api/user', data => {}); // 同、异步返回 Mock.get('/api/user/:id', id => { return { /* 返回数据 data */ }; }); Mock.get('/api/user/:id', id => { return Promise.resolve({ /* 返回数据 data */ }); }); // 延时返回数据, 4秒后传回数据 Mock.get('/api/users', () => { return { /* 返回数据 data */ }; }, {timeout: 4000}); // 异常code返回 Mock.get('/api/users', () => { return { /* 返回数据 data */ }; }, {code: 500});
经过这样的封装,咱们基本已经能够快速模拟客户端的全部请求,并模拟大部分可能性状况对客户端作出响应。在实现过程当中,作了两个特别的处理:
:id
,请求参数分为data和params,data参数针对post请求,params针对get请求,每一种请求只能拦截一种。占位符参数永远优先与请求参数,且data参数和params不会共存。CRUD
。而且咱们支持多种前端数据库,大部分能够连接到IndexDB、WebSQL,此时RouteMock对异步返回的数据有很好的支持的。啥也不说,咱们上代码:
// EXPRESS所用到的路径转正则库 import pathTo from 'path-to-regexp'; // https://github.com/ctimmerm/axios-mock-adapter import MockAdapter from 'axios-mock-adapter'; import Mock from 'mockjs'; // 封装的AJAX库 import { instance } from '@/lib/ajax'; import url from 'url'; import qs from 'qs'; // 实例化数据模拟器 const AdapterMock = new MockAdapter(instance); const mock = { mock: template => Mock.mock(template) }; const httpMethods = ['get', 'post', 'patch', 'put', 'delete', 'head', 'options']; httpMethods.forEach((type) => { const mockMethod = 'on' + type.toLowerCase().replace(/( |^)[a-z]/g, (L) => L.toUpperCase()); mock[type] = function (urlReqExpString, callback, {code = 200, timeout = 0} = {}) { // 将url匹配表达式进行转换, 例如: /path/:id const rurl = pathTo(urlReqExpString, [], { // 不忽略大小写 sensitive: true, strict: true }); // 对get请求进行甄别,由于get请求有`?`参数 if (type === 'get') { // 将url正则两边的`/`去掉,并追加`?`参数的正则校验 const urlString = rurl .toString() .slice(1, -1) .replace('$', '\\/{0,1}(\\?{0}|\\?{1}((\\S+\\={1})(\\S+)\\&{0,1})*)$'); // 从新编译正则规则 rurl.compile(urlString); } AdapterMock[mockMethod](rurl).reply(async config => { const urlSchema = url.parse(config.url); // 拦截的占位符参数,去除`?`校验所带的四组括号,以及第一项url const argsArr = rurl.exec(urlSchema.pathname); const args = argsArr.slice(1, type === 'get' ? argsArr.length - 4 : argsArr.length); // get 提交参数 const params = config.params || qs.parse(urlSchema.query); // post 提交参数 let datas; try { // 对报文进行JSON转换,若是转换失败,说明不是JSON格式,直接返回 datas = JSON.parse(config.data); } catch (e) { datas = config.data; } args.push(type === 'get' ? params : datas); const result = Mock.mock(await callback.call(config, ...args)); // return result === undefined ? {} : result; return new Promise((resolve, reject) => { setTimeout(() => { resolve([code, result]); }, timeout); }); }); }; }); export default mock;
虽然模块的构建已经完成,然而在推广的时候遭遇到巨大的阻力,这也反映了Mock自己所带来的问题——功能鸡肋。由于开发过程当中,某些接口并非一开始就定义好的,即使定义好了也会有修改的可能。一旦有所变化,其代码修改的工做量也是挺大的,模拟数据的地方和接入数据的地方都要改动。
咱们将开发过程分为五个模式:
对于本地开发模式,其实用性仍然比较强的,而且对于本地联调和生产演示都是可复用的,改动量不大的。而接口的改动的确在所不免,这在开发过程当中须要明确以及协调。前端是一个工做量相对于服务端要大的多的工做环境,除了业务逻辑、界面样式、交互流程,还要等待服务端数据进行实时对接,下降这总服务间的耦合,可让前端将关注点真正放在前端工程的构建上,而不是数据。