之前我寫了一篇文章,分享了自己的項目中對於介面管理的方法。總結下來就是:定義介面文件--withAxios導出--調用介面方法。這樣實現了介面的統一管理和調用介面的語義化與簡單化。 根據在項目的使用,發現有以下問題需要優化: 根據以上問題,採用了以下解決方案: 通過代碼展示一下(React項目): ...
之前我寫了一篇文章,分享了自己的項目中對於介面管理的方法。總結下來就是:定義介面文件--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中。這樣也符合我們語義化的目標。