之前我写了一篇文章,分享了自己的项目中对于接口管理的方法。总结下来就是:定义接口文件--withAxios导出--调用接口方法。这样实现了接口的统一管理和调用接口的语义化与简单化。
根据在项目的使用,发现有以下问题需要优化:
- withAxios导出的接口方法对象对编辑器来说是不透明的,所以代码提示功能缺失。
- 同一个方法调用多次,如何保证组件总是获取到最后一次的返回信息。
根据以上问题,采用了以下解决方案:
- 使用typescript的泛型解决。
- 调用同一个方法时,取消掉上次未完成的请求,这里使用axios的cancel方法。实现思路是在返回的方法对象中增加一个`${name}Cancel`的方法,保存取消上一次方法的回调,下次请求时固定调用这个取消方法以保证本次请求是当前唯一一个请求。(这里只提供axios层面的解决办法,不讨论其他办法,比如采用redux-saga的话可以使用takeLatest解决)
通过代码展示一下(React项目):
service.ts
import { IApiItem } from '@/configs/api/declares'; import withAxios from '@/utils/withAxios'; const api: IApiItem[] = [ { name: 'getSummary', url: 'http://xx:8000/api/getSummary' }, { name: 'getDetail', url: 'http://xx:8000/api/getDetail' }, { name: 'getDetailChildren', url: 'http://xx:8000/api/getDetailChildren' }, { name: 'getCurrentUser', url: 'http://xx:8000/api/getCurrentUser' }, ]; interface IProdMonitorApi { getSummary: any; getDetail: any; getDetailChildren: any; getCurrentUser: any; } export default withAxios<IProdMonitorApi>(api);
withAxios.ts
function withAxios<T>(apiConfig: IApiItem[], usePassportOrigin: boolean = false): T { const serviceMap = {} as T; apiConfig.map(({ name, url, method = 'get', ...rest }: IApiItem) => { return (serviceMap[name] = async function(data = {}) { if (serviceMap[`${name}Cancel`] && typeof serviceMap[`${name}Cancel`] === 'function') { serviceMap[`${name}Cancel`](); } const source = axios.CancelToken.source(); serviceMap[`${name}Cancel`] = () => { source.cancel(`已取消上次未完成请求:${name}`); }; rest.cancelToken = source.token; let key = 'params'; const apiMethod = method.toLowerCase(); if (apiMethod === 'post' || apiMethod === 'put') { key = 'data'; } let fetchUrl = url; if (url.indexOf('http') !== 0) { fetchUrl = usePassportOrigin ? NetworkUtils.passportOrigin + url : NetworkUtils.serverOrigin + url; } return axios({ method, url: fetchUrl, [key]: data, fetchName: name, ...rest, } as AxiosRequestConfig); }); }); return serviceMap; } export default withAxios;
在需要使用接口的地方:
import Service from "./service.ts"
Service.getSummary(requestParams).then(...)
说明:
- 使用泛型虽然有了代码提示,但是额外增加了编码量,因为要手动维护一个方法接口,有利有弊吧,通过ts我还没有找到更好的方法。同事之前有过一个解决办法:接口管理使用对象的形式,然后withAxios修改这个对象各属性的getter,将getter指向通过axios包装后的方法,最终实现了本文相同的调用方式和代码提示,但这种方法有点hack的感觉。
- cancel掉上一个接口这种方式保证了数据总是来源于最后一个请求接口,但有时可能会出现问题,比如:在同一个页面需要展示两种用户:common用户和admin用户,后端给的接口是/api/v1/user,当参数type=1时为common,type=2时为admin,如果我们把这个接口定义为一个方法getUser,在这个页面会同时发出两个请求:Service.getUser({type:1}),Service.getUser({type:2}),但是,由于withAxios会取消上一个相同方法的请求,那么很可能有一个请求被取消,解决办法是在service中定义为两种方法:getCommonUser和getAdminUser,将type直接写入url中。这样也符合我们语义化的目标。