在前端項目中肠牲,和后臺交互獲取數(shù)據(jù)這塊竹观,我們通常使用的是axios庫,axios是一個基于
promise
的HTTP庫支救,可運行在client
端和server
端。
雖然axios的使用已經(jīng)很方便了拷淘,但是在實際項目中各墨,為了接口的規(guī)則一致,來創(chuàng)建一個統(tǒng)一管理的全局方法以達到簡化操作的目的启涯。
1.安裝axios
yarn add axios -D
// 或者
npm i axios -D
2.定義需要的接口
因為使用了
typescript
贬堵,所以需要先定義好一些接口,以便在后續(xù)的代碼使用
export type Method = 'GET' | 'POST' | 'PUT' | 'DELETE'
export type ResponseType = 'arraybuffer' | 'blob' | 'document' | 'json' | 'text' | 'stream'
export interface AxiosRequest {
baseURL?: string;
url: string;
data?: any;
params?: any;
method?: Method;
headers?: any;
timeout?: number;
responseType?: ResponseType;
}
export interface AxiosResponse {
data: any;
headers: any;
request?: any;
status: number;
statusText: string;
config: AxiosRequest;
}
export interface CustomResponse {
readonly status: boolean;
readonly message: string;
data: any;
origin?: any;
}
export interface GetDemo {
id: number;
str: string;
}
export interface PostDemo {
id: number;
list: Array<{
id: number;
version: number;
}>;
}
3.創(chuàng)建axios實例结洼,并加入攔截器
根據(jù)指定配置創(chuàng)建一個實例黎做,在這里對重復(fù)的請求做
cancel
處理,還可以對失敗的請求重新發(fā)起請求操作
import axios, { AxiosRequestConfig, Method } from 'axios';
import { Loading } from 'element-ui';
import { ElLoadingComponent } from 'element-ui/types/loading';
// 定義接口
interface PendingType {
url?: string;
method?: Method;
params: any;
data: any;
cancel: Function;
}
// 取消重復(fù)請求
const pending: Array<PendingType> = [];
const CancelToken = axios.CancelToken;
// axios 實例
const instance = axios.create({
timeout: 10000,
responseType: 'json'
});
let loadingInstance: ElLoadingComponent;
// 移除重復(fù)請求
const removePending = (config: AxiosRequestConfig) => {
for (const key in pending) {
const item: number = +key;
const list: PendingType = pending[key];
// 當前請求在數(shù)組中存在時執(zhí)行函數(shù)體
if (list.url === config.url && list.method === config.method && JSON.stringify(list.params) === JSON.stringify(config.params) && JSON.stringify(list.data) === JSON.stringify(config.data)) {
// 執(zhí)行取消操作
list.cancel('操作太頻繁松忍,請稍后再試');
// 從數(shù)組中移除記錄
pending.splice(item, 1);
}
}
};
// 添加請求攔截器
instance.interceptors.request.use(
request => {
loadingInstance = Loading.service({
text: '加載中',
background: 'rgba(0, 0, 0, 0.3)'
});
removePending(request);
request.cancelToken = new CancelToken((c) => {
pending.push({ url: request.url, method: request.method, params: request.params, data: request.data, cancel: c });
});
return request;
},
error => {
return Promise.reject(error);
}
);
// 添加響應(yīng)攔截器
instance.interceptors.response.use(
response => {
loadingInstance.close();
removePending(response.config);
const errorCode = response?.data?.errorCode;
switch (errorCode) {
case '401':
// 根據(jù)errorCode蒸殿,對業(yè)務(wù)做異常處理(和后端約定)
break;
default:
break;
}
return response;
},
error => {
loadingInstance.close();
const response = error.response;
// 根據(jù)返回的http狀態(tài)碼做不同的處理
switch (response?.status) {
case 401:
// token失效
break;
case 403:
// 沒有權(quán)限
break;
case 500:
// 服務(wù)端錯誤
break;
case 503:
// 服務(wù)端錯誤
break;
default:
break;
}
// 超時重新請求
const config = error.config;
// 全局的請求次數(shù),請求的間隙
const [RETRY_COUNT, RETRY_DELAY] = [3, 1000];
if (config && RETRY_COUNT) {
// 設(shè)置用于跟蹤重試計數(shù)的變量
config.__retryCount = config.__retryCount || 0;
// 檢查是否已經(jīng)把重試的總數(shù)用完
if (config.__retryCount >= RETRY_COUNT) {
return Promise.reject(response || { message: error.message });
}
// 增加重試計數(shù)
config.__retryCount++;
// 創(chuàng)造新的Promise來處理指數(shù)后退
const backoff = new Promise((resolve) => {
setTimeout(() => {
resolve();
}, RETRY_DELAY || 1);
});
// instance重試請求的Promise
return backoff.then(() => {
return instance(config);
});
}
// eslint-disable-next-line
return Promise.reject(response || {message: error.message});
}
);
export default instance;
4.配置api字典表
制定一個url的字典表,利于后期的更新維護
/**
* API URL Dict api 字典
*/
interface UrlDict {
[key: string]: {
[key: string]: string;
};
}
const urlDict: UrlDict = {
Basic: {
GetDemo: 'admin/get',
PostDemo: 'admin/post',
}
};
const getUrl = (biz: string, UrlName: string): string => {
try {
const bizKeys = Object.keys(urlDict);
if (bizKeys.indexOf(biz) < 0) {
throw new Error('biz not in Dict');
}
let hostname = urlDict[biz][UrlName];
if (!hostname) {
throw new Error('url not in Dict');
}
if (hostname.substr(0, 1) === '/') {
hostname = hostname.substr(1);
}
return hostname;
} catch (err) {
console.error(err);
return '';
}
};
export default getUrl;
5.封裝axios的基類
封裝一個基類,對子類暴露
get
宏所,post
酥艳,put
,delete
方法楣铁。便于子類的繼承使用
/**
* axios基礎(chǔ)構(gòu)建
* @date 2019-12-24
*/
import Vue from 'vue';
import getUrl from './config';
import instance from './intercept';
import { AxiosRequest, CustomResponse } from './types';
class Abstract {
// 外部傳入的baseUrl
protected baseURL: string = process.env.VUE_APP_BaseURL;
// 自定義header頭
protected headers: object = {
ContentType: 'application/json;charset=UTF-8'
}
private apiAxios({ baseURL = this.baseURL, headers = this.headers, method, url, data, params, responseType }: AxiosRequest): Promise<CustomResponse> {
// url解析
const _url = (url as string).split('.');
url = getUrl(_url[0], _url[1]);
return new Promise((resolve, reject) => {
instance({
baseURL,
headers,
method,
url,
params,
data,
responseType
}).then((res) => {
// 200:服務(wù)端業(yè)務(wù)處理正常結(jié)束
if (res.status === 200) {
if (res.data.success) {
resolve({ status: true, message: 'success', data: res.data?.data, origin: res.data });
} else {
Vue.prototype.$message({ type: 'error', message: res.data?.errorMessage || (url + '請求失敗') });
resolve({ status: false, message: res.data?.errorMessage || (url + '請求失敗'), data: res.data?.data, origin: res.data });
}
} else {
resolve({ status: false, message: res.data?.errorMessage || (url + '請求失敗'), data: null });
}
}).catch((err) => {
const message = err?.data?.errorMessage || err?.message || (url + '請求失敗');
Vue.prototype.$message({ type: 'error', message });
// eslint-disable-next-line
reject({ status: false, message, data: null});
});
});
}
/**
* GET類型的網(wǎng)絡(luò)請求
*/
protected getReq({ baseURL, headers, url, data, params, responseType }: AxiosRequest) {
return this.apiAxios({ baseURL, headers, method: 'GET', url, data, params, responseType });
}
/**
* POST類型的網(wǎng)絡(luò)請求
*/
protected postReq({ baseURL, headers, url, data, params, responseType }: AxiosRequest) {
return this.apiAxios({ baseURL, headers, method: 'POST', url, data, params, responseType });
}
/**
* PUT類型的網(wǎng)絡(luò)請求
*/
protected putReq({ baseURL, headers, url, data, params, responseType }: AxiosRequest) {
return this.apiAxios({ baseURL, headers, method: 'PUT', url, data, params, responseType });
}
/**
* DELETE類型的網(wǎng)絡(luò)請求
*/
protected deleteReq({ baseURL, headers, url, data, params, responseType }: AxiosRequest) {
return this.apiAxios({ baseURL, headers, method: 'DELETE', url, data, params, responseType });
}
}
export default Abstract;
6.定義具體的業(yè)務(wù)請求
/**
* 基礎(chǔ)數(shù)據(jù) API 集合類
* 集成Abstract
* @date 2020-1-14
*/
import Abstract from '../abstract';
import { GetDemo, PostDemo } from './types';
class Basic extends Abstract {
/**
* get示例
*/
getDemo(params: GetDemo) {
return this.getReq({ url: 'Basic.GetDemo', params });
}
/**
* post示例
*/
postDemo(data: PostDemo) {
return this.postReq({ url: 'Basic.PostDemo', data });
}
}
// 單列模式返回對象
let instance;
export default (() => {
if (instance) return instance;
instance = new Basic();
return instance;
})();
以上就是我對axios的封裝玖雁,有啥不完善的地方,歡迎大家指正批評
附上 github 的工程地址:vue+ts盖腕,順手給樓主點個 star 吧