现状描述
我们一般在做vue或者react的单页面项目的时候,对所有接口的请求的管理一般都是创建一个公共的js,里面导出许多返回一个Promise的函数。
例如我在src/api目录下创建了一个common.js,代码如下:(注:request方法默认的Content-Type为application/json)
import request from '@/utils/request';
import qs from 'qs';
/***** 分组 *****/
// 分组列表
export function groupList(params) {
return request({
url: '/zm/group',
method: 'get',
params,
});
}
// 分组详情
export function groupDetail(id) {
return request({
url: `/zm/group/${id}`,
method: 'get',
});
}
// 分组新增
export function groupAdd(data) {
return request({
url: '/zm/group',
method: 'post',
data,
});
}
// 分组修改
export function groupUpdate(id, data) {
return request({
url: `/zm/group/${id}`,
method: 'put',
data,
});
}
// 分组删除
export function groupDelete(id) {
return request({
url: `/zm/group/${id}`,
method: 'delete',
});
}
/***** 应用 *****/
// 应用列表
export function applicationList(fromType, unitSetId, params) {
return request({
url: `/zm/${fromType}/${unitSetId}/application`,
method: 'get',
params,
});
}
// 应用详情
export function applicationDetail(fromType, unitSetId, id) {
return request({
url: `/zm/${fromType}/${unitSetId}/application/${id}`,
method: 'get',
});
}
// 应用新增
export function applicationAdd(fromType, unitSetId, data) {
return request({
url: `/zm/${fromType}/${unitSetId}/application`,
method: 'post',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
transformRequest: [(data) => qs.stringify(data)],
data,
});
}
// 应用修改
export function applicationUpdate(dOpts, data) {
const { fromType, unitSetId, id } = dOpts;
return request({
url: `/zm/${fromType}/${unitSetId}/application/${id}`,
method: 'put',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
transformRequest: [(data) => qs.stringify(data)],
data,
});
}
/***** 文件上传 *****/
// 图片上传通用接口
export function sysUpload(formdata) {
return request({
url: '/zm/sys/upload',
method: 'post',
headers: {
'Content-Type': 'multipart/form-data',
},
data: formdata,
});
}
这也是我经常在一些项目中看到的一种方式,虽然这样做到对接口的统一管理,但是从我的角度看,还是有一些不足的地方:
- 接口数不太多,但是代码量却拉的很长,因为重复的代码太多了,例如 export function、return request等。
- Content-Type不同时,需要额外费点精力处理下。
- 因为只要是一个返回一个Promise的函数,导致写代码时个人自主性太强,很难形成一个规范,多人进行开发任务的时候,由于没有一个统一规范,不利于统一管理和维护。例如上面应用新增这个接口,某个开发人员把data这个形参放在第一个,后续其他人接手开发的时候,就很可能产生问题。
使用Proxy写接口请求服务
Proxy 对象可以拦截目标对象的任意属性,这所以很适用来写请求接口的服务。直接上代码,在src/service/index.js里的代码如下:
import qs from 'qs';
import request from '@/utils/request';
// 说明: 'post/2', 'put/2', 'patch/2' 表示Content-Type为application/x-www-form-urlencode的请求
export default new Proxy(
{
/***** 分组 *****/
// #region
// 分组列表
groupList: 'get /zm/group',
// 分组详情
groupDetail: { method: 'get', url: (id) => `/zm/group/${id}` },
// 分组新增
groupAdd: 'post /zm/group',
// 分组修改
groupUpdate: { method: 'put', url: (id) => `/zm/group/${id}` },
// 分组删除
groupDel: { method: 'delete', url: (id) => `/zm/group/${id}` },
// 所有分组列表
groupAll: 'get /zm/group/all',
// #endregion
/***** 应用 *****/
// #region
// 应用列表
applicationList: {
method: 'get',
url: (fromType, unitSetId) => `/zm/${fromType}/${unitSetId}/application`,
},
// 应用详情
applicationDetail: {
method: 'get',
url: (fromType, unitSetId, id) => `/zm/${fromType}/${unitSetId}/application/${id}`,
},
// 应用新增
applicationAdd: {
method: 'post/2',
url: (fromType, unitSetId) => `/zm/${fromType}/${unitSetId}/application`,
config: { headers: { 'i-custom-header': 'ftx' } },
},
// 应用修改
applicationUpdate: {
method: 'put/2',
url: (fromType, unitSetId, id) => `/zm/${fromType}/${unitSetId}/application/${id}`,
},
// #endregion
/***** 文件上传 *****/
// #region
// 图片上传通用接口
sysUpload: 'fileupload /zm/sys/upload',
// #endregion
},
{
cache: new Map(),
handleRequest(url, method, conf) {
if (['get', 'post', 'put', 'patch', 'delete'].includes(method)) {
const dkey = ['get', 'delete'].includes(method) ? 'params' : 'data';
return (params, config) =>
request({
url,
method,
[dkey]: params,
...conf,
...config,
});
} else if (['post/2', 'put/2', 'patch/2'].includes(method)) {
const [m] = method.split('/');
return (params, config) =>
request({
url,
method: m,
data: params,
...conf,
...config,
headers: {
...(config?.headers ? config?.headers : conf?.headers),
'Content-Type': 'application/x-www-form-urlencoded',
},
transformRequest: [(data) => qs.stringify(data)],
});
} else if (method === 'fileupload') {
return (params, config) =>
request({
url,
method: 'post',
data: params,
...conf,
...config,
headers: {
...(config?.headers ? config?.headers : conf?.headers),
'Content-Type': 'multipart/form-data',
},
});
}
},
get(target, prop, receiver) {
const currentCache = this.cache;
const handleRequest = this.handleRequest;
if (currentCache.has(prop)) return currentCache.get(prop);
let fun;
const methodList = [
'get',
'post',
'put',
'patch',
'delete',
'post/2',
'put/2',
'patch/2',
'fileupload',
];
if (typeof target[prop] === 'string') {
const [method, url] = target[prop].split(' ');
if (methodList.includes(method)) fun = handleRequest(url, method);
} else if (Object.prototype.toString.call(target[prop]) === '[object Object]') {
const { method, url, config: config = {} } = target[prop];
if (methodList.includes(method)) {
if (typeof url === 'string') {
fun = handleRequest(url, method, config);
} else if (typeof url === 'function') {
fun = (...args) => {
if (Array.isArray(args[0])) {
// args[0]是一个数组,是拼接url需要的参数 args[1]为请求参数 args[2]为config参数
return handleRequest(url(...args[0]), method, config)(args[1], args[2]);
} else {
return handleRequest(url(...args), method, config)();
}
};
}
}
}
if (fun) {
currentCache.set(prop, fun);
return fun;
}
return Reflect.get(target, prop, receiver);
},
set() {
throw new Error('error');
},
},
);
增加了一个缓存,提高性能。
那么我们请求接口的时候我们就可以这样去请求:
import service from '@/service';
export default {
methods: {
// 请求分组列表接口
async groupList() {
const res = await service.groupList({ name: '', limit: 10, page: 1 });
},
// 请求分组详情接口
async groupDetail() {
const res = await service.groupDetail(123);
},
// 请求分组新增接口
async groupAdd() {
const res = await service.groupAdd({ name: 'xxxx', value: 2222 });
},
// 请求分组修改接口
async groupUpdate() {
const res = await service.groupUpdate([123], { name: 'xxxx', value: 333 });
},
// 请求分组删除接口
async groupDelete() {
const res = await service.groupDelete(123, { headers: { token: '6utm3UBVGS=' } });
},
// 请求应用列表接口
async applicationList() {
const res = await service.applicationList(['from', 234], { name: '', limit: 10, page: 1 });
},
// 请求应用详情接口
async applicationDetail() {
const res = await service.applicationDetail('from', 234, 123);
},
// 请求应用新增接口
async applicationAdd() {
const res = await service.applicationAdd(['from', 234], { name: 'xxxx', value: 2222 });
},
// 请求应用修改接口
async applicationUpdate() {
const res = await service.applicationUpdate(['from', 234, 123], { name: 'xxxx', value: 333 });
},
// 请求图片上传通用接口
async sysUpload(File) {
const formdata = new FormData();
formdata.append('file', File);
formdata.append('name', 'e123');
const res = await service.sysUpload(formdata);
},
},
};
如果你的项目中有需要jsonp的请求方式,或者是app内嵌的h5需要调用native的方法,都可以用类似的方法处理,例如这样的去做处理(写的比较简单):
import jsonp from '@/utils/jsonp';
import util from '@/utils/util';
export default new Proxy(
{
authStatus: 'jsonp /user/authStatus',
getClientInfo: 'nativepost /client/getClientInfo',
},
{
get(target, prop, receiver) {
if (typeof target[prop] === 'string') {
const [method, url] = target[prop].split(' ');
if (method === 'jsonp') return (params, conf) => jsonp(url, params, conf);
if (method === 'nativepost') {
if (util.isIos()) {
return (params, conf) => IosNativePost(url, params, conf);
} else if (util.isAndroid()) {
return (params, conf) => AndroidNativePost(url, params, conf);
}
}
}
return Reflect.get(target, prop, receiver);
},
},
);
使用Proxy的方式还有许多的好处,例如:1、在请求接口之前做拦截,就拿我举例子,我以前做的一个项目,需要接入单点登录,单点架构部门是通过提供一个jsonp的接口,如果请求需要鉴权的接口,让我们去请求他们的服务,然后会返回登录是否有权限或者超时,如果超时需要我们自己跳转到单点登录页。对于这种需求用Proxy很方便,在需要鉴权的接口之前请求下认证接口,如果认证通过则返回函数,认证不通过跳转登录页。 2、做vue、react ssr的情况下请求接口,比如请求的接口的时候需要携带cookie认证信息,一般我们是使用axios作http库(因为它可以从node.js创建 http 请求),在node环境中需要加入{ headers: { cookie: 'xxx=www' } },但是在浏览器中是不能有这段代码的,通过使用Proxy拦截的方式,很容易就能解决这么个问题。
以上,内容到这里就介绍完了。