TypeScript封裝Axios
Axios的基本使用
因axios基礎(chǔ)使用十分簡(jiǎn)單,可參考axios官方文檔,這里不在介紹他基本用法,主要講解攔截器。
攔截器主要分為兩種,請(qǐng)求攔截器和響應(yīng)攔截器。
請(qǐng)求攔截器:請(qǐng)求發(fā)送之前進(jìn)行攔截,應(yīng)用于我們?cè)谡?qǐng)求發(fā)送前需要對(duì)請(qǐng)求數(shù)據(jù)做一些處理。例如:
- 攜帶token
- 當(dāng)請(qǐng)求時(shí)間過(guò)長(zhǎng)時(shí),設(shè)置loading
響應(yīng)攔截器:在響應(yīng)到達(dá)時(shí)進(jìn)行攔截,應(yīng)用于在我們業(yè)務(wù)代碼中拿到數(shù)據(jù)之前,需要對(duì)數(shù)據(jù)做一定處理。例如:
- 轉(zhuǎn)換數(shù)據(jù)格式
- 移除loading
為什么要封裝Axios
在項(xiàng)目中會(huì)有很多的模塊都需要發(fā)送網(wǎng)絡(luò)請(qǐng)求,常見(jiàn)的比如登錄模塊,首頁(yè)模塊等,如果我們項(xiàng)目中直接使用諸如axios.get(), axios.post(),會(huì)存在很多弊端,哪些弊端呢?
- 首先這樣做會(huì)導(dǎo)致我們每個(gè)模塊對(duì)axios依賴性太強(qiáng),意味著我們項(xiàng)目中的每個(gè)模塊都和一個(gè)第三方庫(kù)耦合度較高,這樣的話,如果axios不在維護(hù),我們要更換庫(kù)的時(shí)候?qū)⒎浅B闊?,我們可以假設(shè)一下,隨著時(shí)間的推移,axios可能因?yàn)闉g覽器的升級(jí),Webpack的改變而出現(xiàn)一些bug, 然而axios已不再維護(hù),這時(shí)我們往往需要切換庫(kù),這就意味著我們需要去修改每個(gè)模塊中的請(qǐng)求相關(guān)的代碼,顯而易見(jiàn),非常繁瑣。
- 還有一點(diǎn),在我們發(fā)送網(wǎng)絡(luò)請(qǐng)求的時(shí)候,往往會(huì)有很多共同的特性,比如說(shuō),在我們成功登錄之后的其他請(qǐng)求中,我們往往需要在請(qǐng)求頭中添加token,然后發(fā)送請(qǐng)求;在每次請(qǐng)求中,我們想展示一個(gè)loading… 這些功能如果在每次請(qǐng)求的邏輯中都寫一遍,很明顯,我們的代碼重復(fù)度太高了。
而axios封裝之后,則會(huì)帶來(lái)很多好處:
解決以上弊端,降低與第三方庫(kù)的耦合度,這樣我們將來(lái)需要更換庫(kù)時(shí),只需要修改我們封裝后的request即可,這樣我們往往只是修改封裝后一兩個(gè)文件,而不再需要每個(gè)模塊每個(gè)模塊的修改。
在我們開(kāi)發(fā)中,我認(rèn)為class的相關(guān)語(yǔ)法封裝性會(huì)更好,因此這里我選擇嘗試用類相關(guān)的概念來(lái)封裝axios。我想要的封裝后達(dá)到的效果:可以直接在其他項(xiàng)目使用。
利用面向?qū)ο蟮乃枷雽?duì)Axios進(jìn)行封裝
基礎(chǔ)封裝
封裝一個(gè)Request的類,使得在外部可以調(diào)用此類的構(gòu)造函數(shù)創(chuàng)建實(shí)例,創(chuàng)建的實(shí)例就對(duì)應(yīng)axios實(shí)例,http/request.ts
中代碼如下:
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios";
// 創(chuàng)建這個(gè)類的目的:每個(gè)創(chuàng)建出的HDRequest的實(shí)例都對(duì)應(yīng)一個(gè)axios實(shí)例
class Request {
// 創(chuàng)建實(shí)例的方法:constructor()構(gòu)造實(shí)例
instance: AxiosInstance;
constructor(config: AxiosRequestConfig) {
this.instance = axios.create(config);
}
// 二次封裝網(wǎng)絡(luò)請(qǐng)求的方法
request(config: AxiosRequestConfig) {
return this.instance.request(config);
}
}
// 暴露Request類
export default Request;
基本配置信息單獨(dú)寫在一個(gè)文件中,config/index.ts
中代碼如下:
const CONFIG = {
// 服務(wù)器地址
serverAddress: 'https://91huajian.cn',
// 其他基礎(chǔ)配置項(xiàng),入最長(zhǎng)響應(yīng)時(shí)間等
};
export default CONFIG;
在http/index.ts
中創(chuàng)建一個(gè)Request
類的一個(gè)實(shí)例http
,并配置這個(gè)實(shí)例:
import Request from "./index";
import CONFIG from "@/config";
// 創(chuàng)建一個(gè)axios實(shí)例
const http = new Request({
baseURL: CONFIG.serverAddress,
timeout: CONFIG.maxTimeout,
})
export default http;
在接口中使用該實(shí)例發(fā)送請(qǐng)求:
// 在http/api/sponsor.ts文件中封裝發(fā)送請(qǐng)求的方法,在頁(yè)面組件任意位置隨意調(diào)用
import http from '../request';
// 查詢贊助
export const getSponsorListAsync: any = (params: any) => {
return http.request({
url: '/huajian/common/getSponsorList',
method: 'get',
params: params
});
}
攔截器的類型
攔截器分為三種:
- 類攔截器(在封裝的axios類(文中類為
Request
類)上定義的攔截器)- 實(shí)例攔截器(在利用
Request
類實(shí)例化對(duì)象時(shí)傳遞的參數(shù)中定義的攔截器)- 接口攔截器(在調(diào)用實(shí)例時(shí)傳入的參數(shù)中定義的參數(shù))
配置全局?jǐn)r截器(類攔截)
保證每一個(gè)axios實(shí)例對(duì)象都有攔截器,即本使用Request實(shí)例化的對(duì)象發(fā)送的請(qǐng)求都會(huì)被配置的攔截器所攔截并執(zhí)行攔截器中的程序。
類攔截器比較容易實(shí)現(xiàn),只需要在類中對(duì)axios.create()
創(chuàng)建的實(shí)例調(diào)用interceptors
下的兩個(gè)攔截器即可,實(shí)例代碼如下:
import axios, { AxiosInstance,AxiosRequestConfig,AxiosResponse } from 'axios';
import { RequestConfig } from './types/types';
class Request {
instance: AxiosInstance;
constructor(config: RequestConfig) {
this.instance = axios.create(config);
// 添加全局請(qǐng)求攔截器,每個(gè)實(shí)例都有
this.instance.interceptors.request.use(
// 攔截到請(qǐng)求中攜帶的所有配置項(xiàng)config
(config: AxiosRequestConfig) => {
console.log('全局請(qǐng)求攔截器', config);
return config;
},
(err: any) => err
)
// 添加全局響應(yīng)攔截器,每個(gè)實(shí)例都有
this.instance.interceptors.response.use(
// 攔截到服務(wù)器返回的響應(yīng)體res
(res: AxiosResponse) => {
console.log('全局響應(yīng)攔截器', res);
return res.data;
},
(err: any) => err;
}
request(config: AxiosRequestConfig) {
return this.instance.request(config);
}
}
export default Request;
我們?cè)谶@里對(duì)響應(yīng)攔截器做了一個(gè)簡(jiǎn)單的處理,就是將請(qǐng)求結(jié)果中的.data
進(jìn)行返回,因?yàn)槲覀儗?duì)接口請(qǐng)求的數(shù)據(jù)主要是存在在.data
中,跟data
同級(jí)的屬性我們基本是不需要的。
為某一Request實(shí)例單獨(dú)配置攔截器(實(shí)例攔截)
實(shí)例攔截器是為了保證封裝的靈活性,因?yàn)槊恳粋€(gè)實(shí)例中的攔截后處理的操作可能是不一樣的,所以在定義實(shí)例時(shí),允許我們傳入攔截器。
新創(chuàng)建一個(gè)實(shí)例http2
在它的config
中傳入攔截器屬性,但是axios
的AxiosRequestConfig
類型中并沒(méi)有攔截器屬性類型。
因此需要對(duì)types/index.ts
中的構(gòu)造函數(shù)中的config
類型進(jìn)行擴(kuò)展(extends)。首先我們定義一下interface,方便類型提示,代碼如下:
import { AxiosRequestConfig, AxiosResponse } from "axios";
// 攔截器的類型
export interface RequestInterceptors<T> {
// 請(qǐng)求攔截器
requestInterceptor?: (config: AxiosRequestConfig) => AxiosRequestConfig;// 在發(fā)送請(qǐng)求之前做些什么
requestInterceptorCatch?: (error: any) => any;// 對(duì)請(qǐng)求錯(cuò)誤做些什么
// 響應(yīng)攔截器
responseInterceptor?: (res: T) => T;// 對(duì)響應(yīng)數(shù)據(jù)做點(diǎn)什么
responseInterceptorsCatch?: (err: any) => any;// 對(duì)響應(yīng)錯(cuò)誤做點(diǎn)什么
}
// 自定義傳入的參數(shù)
export interface RequestConfig<T = AxiosResponse> extends AxiosRequestConfig{
interceptors?: RequestInterceptors<T>;
}
然后再創(chuàng)建新的實(shí)例,并在實(shí)例中引入定義的攔截器:
import Request from "./index";
import CONFIG from "@/config";
import {RequestConfig} from "./types/types";
import { AxiosResponse } from "axios";
// 創(chuàng)建一個(gè)axios實(shí)例
const http = new Request({
baseURL: CONFIG.serverAddress,
timeout: CONFIG.maxTimeout
})
const http2 = new Request({
baseURL: CONFIG.serverAddress,
timeout: CONFIG.maxTimeout,
interceptors: {
// 配置請(qǐng)求攔截器
requestInterceptor: (config: RequestConfig) => {
console.log('通過(guò)請(qǐng)求攔截器,拿到http2的請(qǐng)求配置參數(shù)',config);
return config;
},
// 響應(yīng)攔截器
responseInterceptor: (result: AxiosResponse) => {
console.log('通過(guò)響應(yīng)攔截器,拿到http2的響應(yīng)返回的結(jié)果',result);
return result;
}
}
})
export default {http,http2};
注意:這里的攔截器只能由使用http2
實(shí)例發(fā)送的請(qǐng)求才會(huì)執(zhí)行。
我們的攔截器的執(zhí)行順序?yàn)閷?shí)例請(qǐng)求→類請(qǐng)求→實(shí)例響應(yīng)→類響應(yīng);這樣我們就可以在實(shí)例攔截上做出一些不同的攔截,
此時(shí)在使用Request
實(shí)例化對(duì)象http2
時(shí),我們傳入的配置項(xiàng)中多了interceptors
配置項(xiàng),那么在Request
類中我們就得接收并在實(shí)例化時(shí)執(zhí)行:
import axios, { AxiosInstance,AxiosRequestConfig,AxiosResponse } from 'axios';
import { RequestConfig,RequestInterceptors } from './types/types';
class Request {
instance: AxiosInstance;
// 攔截器對(duì)象
interceptorsObj?: RequestInterceptors<AxiosResponse>;
constructor(config: RequestConfig) {
this.instance = axios.create(config);
this.interceptorsObj = config.interceptors;//接收實(shí)例對(duì)象傳入的該實(shí)例的定制攔截器
// 全局請(qǐng)求攔截器
this.instance.interceptors.request.use(
(config: AxiosRequestConfig) => {
console.log('全局請(qǐng)求成功攔截器', config);
return config;
},
(err: any) => err
)
// 使用實(shí)例對(duì)象的自定義攔截器 針對(duì)特定的http2實(shí)例添加攔截器
this.instance.interceptors.request.use(
this.interceptorsObj?.requestInterceptor, // 請(qǐng)求前的攔截器
this.interceptorsObj?.requestInterceptorCatch // 發(fā)送請(qǐng)求失敗的攔截器
)
// 使用實(shí)例對(duì)象的自定義攔截器 針對(duì)特定的http2實(shí)例添加攔截器
this.instance.interceptors.response.use(
config.interceptors?.responseInterceptor,
config.interceptors?.responseInterceptorsCatch
);
// 全局響應(yīng)攔截器
this.instance.interceptors.response.use(
(res: AxiosResponse) => {
console.log('全局響應(yīng)成功攔截器', res);
return res.data;
},
(err: any) => {
return err;
}
);
}
request(config: AxiosRequestConfig) {
return this.instance.request(config);
}
}
export default Request;
同一個(gè)request實(shí)例的不同網(wǎng)絡(luò)請(qǐng)求設(shè)置不同的攔截器(接口攔截)
現(xiàn)在我們對(duì)單一接口進(jìn)行攔截操作,首先我們將AxiosRequestConfig
類型修改為RequestConfig
允許傳遞攔截器;然后我們?cè)陬悢r截器中將接口請(qǐng)求的數(shù)據(jù)進(jìn)行了返回,也就是說(shuō)在request()
方法中得到的類型就不是AxiosResponse
類型了。
接口中同一個(gè)實(shí)例在發(fā)送不同的request
請(qǐng)求時(shí)一個(gè)配置了攔截器一個(gè)沒(méi)配攔截器
import { http2 } from "..";
http2.request({
url: "/entire/list",
params: {
offset: 0,
size: 20,
},
})
.then((res) => {
console.log(res);
});
http2.request({
url: "/home/highscore",
interceptors: {
responseInterceptor: (config) => {
console.log("來(lái)自接口定制的請(qǐng)求前的攔截");
return config;
},
responseInterceptor: (res) => {
console.log("來(lái)自接口的響應(yīng)成功的攔截");
return res;
},
},
})
.then((res) => {
console.log(res);
});
對(duì)request/index.ts
的request
方法進(jìn)行進(jìn)一步封裝,使之能夠立即執(zhí)行傳進(jìn)來(lái)的攔截器:
// Request類的request方法:
// 二次封裝網(wǎng)絡(luò)請(qǐng)求的方法
request<T>(config: RequestConfig<T>): Promise<T> {
return new Promise((resolve, reject) => {
// 為同一個(gè)request實(shí)例的不同網(wǎng)絡(luò)請(qǐng)求設(shè)置不同的攔截器
// 不能將攔截器放在實(shí)例上,這樣的話同一個(gè)實(shí)例的攔截器都是一樣的了
// 只能判斷傳進(jìn)來(lái)的config中是否設(shè)置了攔截器,若設(shè)置了就直接執(zhí)行
// 執(zhí)行this.instance.request(config)之前先執(zhí)行requestInterceptor,并更新config
if (config.interceptors?.requestInterceptor) {
//立即調(diào)用攔截器函數(shù)執(zhí)行
config = config.interceptors.requestInterceptor(config);
}
// 由于執(zhí)行完this.instance.request(config)之后才能對(duì)response結(jié)果進(jìn)行攔截,是個(gè)異步的過(guò)程
// 在Promise內(nèi)部調(diào)用instance實(shí)例先執(zhí)行this.instance.request(config),然后等待結(jié)果,之后以結(jié)果作為攔截器函數(shù)的參數(shù)進(jìn)行調(diào)用
this.instance
.request<any, T>(config)
.then((res) => {
// 如果給單個(gè)響應(yīng)設(shè)置攔截器,這里使用單個(gè)響應(yīng)的攔截器
if (config.interceptors?.responseInterceptor) {
res = config.interceptors.responseInterceptor(res);
}
resolve(res);
})
.catch((err: any) => {
reject(err);
})
})
}
各種請(qǐng)求攔截的執(zhí)行順序:
攔截器執(zhí)行順序:接口請(qǐng)求 -> 實(shí)例請(qǐng)求 -> 全局請(qǐng)求 -> 實(shí)例響應(yīng) -> 全局響應(yīng) -> 接口響應(yīng)
實(shí)例請(qǐng)求和全局請(qǐng)求的先后順序取決于在Request
類constructor()
構(gòu)造函數(shù)中兩種請(qǐng)求的執(zhí)行順序。
取消請(qǐng)求
思路步驟:
- 創(chuàng)建一個(gè)數(shù)組用于存儲(chǔ)控制器資源;
- 在請(qǐng)求攔截器中將控制器存入數(shù)組;
- 在響應(yīng)攔截器中將控制器從數(shù)組中移除;
- 封裝一個(gè)取消全部請(qǐng)求的方法;
- 封住一個(gè)可以取消指定請(qǐng)求的方法;
準(zhǔn)備
我們需要將所有請(qǐng)求的取消方法保存到一個(gè)集合(這里我用的數(shù)組,也可以使用Map)中,然后根據(jù)具體需要去調(diào)用這個(gè)集合中的某個(gè)取消請(qǐng)求方法。
因此,我們首先進(jìn)行類型定義:
// 一個(gè)取消請(qǐng)求對(duì)象,鍵位url,值為取消控制器
export interface CancelRequestSource {
// 取消請(qǐng)求的標(biāo)識(shí)
[index: string]: AbortController;
}
然后我們?cè)赗equest類中定義儲(chǔ)存取消請(qǐng)求對(duì)象的數(shù)組,和存放請(qǐng)求url的數(shù)組文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-671164.html
/*
存放取消控制對(duì)象的集合
* 在創(chuàng)建請(qǐng)求后將取消控制對(duì)象 push 到該集合中
* 封裝一個(gè)方法,可以取消請(qǐng)求,傳入 url: string|string[]
* 在請(qǐng)求之前判斷同一URL是否存在,如果存在就取消請(qǐng)求
*/
cancelRequestSourceList ?: CancelRequestSource[];
/*
存放所有請(qǐng)求URL的集合
* 請(qǐng)求之前需要將url push到該集合中
* 請(qǐng)求完畢后將url從集合中刪除
* 添加在發(fā)送請(qǐng)求之前完成,刪除在響應(yīng)之后刪除
*/
requestUrlList ?: string[];
接著我們要準(zhǔn)備兩個(gè)方法,一個(gè)時(shí)根據(jù)url在取消控制對(duì)象數(shù)組中找到對(duì)應(yīng)請(qǐng)求的方法,另一個(gè)時(shí)完成取消請(qǐng)求后刪除存放url數(shù)組和存放取下請(qǐng)求對(duì)象數(shù)組中對(duì)象請(qǐng)求的方法。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-671164.html
//根據(jù)url找到取消請(qǐng)求對(duì)象數(shù)組中此次請(qǐng)求取消對(duì)象存放的地址
private getSourceIndex(url: string): number {
return this.cancelRequestSourceList?.findIndex((item: CancelRequestSource) => {
return Object.keys(item)[0] === url;
}) as number;
}
//請(qǐng)求取消完成后,我們要?jiǎng)h除對(duì)應(yīng)請(qǐng)求和取消請(qǐng)求對(duì)象
private delUrl(url: string) {
const urlIndex = this.requestUrlList?.findIndex((u) => u === url);
const sourceIndex = this.getSourceIndex(url);
// 刪除url和AbortController對(duì)象
urlIndex !== -1 && this.requestUrlList?.splice(urlIndex as number, 1);
sourceIndex !== -1 && this.cancelRequestSourceList?.splice(sourceIndex as number, 1);
}
在發(fā)送請(qǐng)求前存入AbortController
對(duì)象
const url = config.url;
// url存在 保存當(dāng)前請(qǐng)求url 和 取消請(qǐng)求方法
if (url) {
this.requestUrlList?.push(url);//將url存入url數(shù)組
const controller = new AbortController();//構(gòu)造實(shí)例化一個(gè)AbortController對(duì)象控制器
config.signal = controller.signal//綁定請(qǐng)求
this.cancelRequestSourceList?.push({
[url]: controller//將該控制器添加入cancelRequestSourceList數(shù)組
})
}
請(qǐng)求已經(jīng)完成了刪除保存的url和AbortController對(duì)象
this.instance
.request<any, T>(config)
.then(res => {
// 如果我們?yōu)閱蝹€(gè)響應(yīng)設(shè)置攔截器,這里使用單個(gè)響應(yīng)的攔截器
if (config.interceptors?.responseInterceptor) {
res = config.interceptors.responseInterceptor<T>(res)
}
resolve(res)
})
.catch((err: any) => {
reject(err)
})
.finally(() => {
url && this.delUrl(url);// 請(qǐng)求執(zhí)行完畢,刪除保存在數(shù)組中的url和該請(qǐng)求的取消方法
});
封裝取消請(qǐng)求方法
- 封裝取消全部請(qǐng)求
// 取消全部請(qǐng)求
cancelAllRequest() {
this.cancelRequestSourceList?.forEach((source) => {
const key = Object.keys(source)[0];
source[key].abort();
})
}
- 封裝取消部分請(qǐng)求
// 取消請(qǐng)求
cancelRequest(url: string | string[]) {
if (typeof url === 'string') {
// 取消單個(gè)請(qǐng)求
const sourceIndex = this.getSourceIndex(url);
sourceIndex >= 0 && this.cancelRequestSourceList?.[sourceIndex][url].abort();
} else {
// 存在多個(gè)需要取消請(qǐng)求的地址
url.forEach((u) => {
const sourceIndex = this.getSourceIndex(u);
sourceIndex >= 0 && this.cancelRequestSourceList?.[sourceIndex][u].abort();
});
}
}
到了這里,關(guān)于TypeScript封裝Axios的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!