axios 封裝
對(duì)請(qǐng)求的封裝在實(shí)際項(xiàng)目中是十分必要的,它可以讓我們統(tǒng)一處理 http 請(qǐng)求。比如做一些攔截,處理一些錯(cuò)誤等。本篇文章將詳細(xì)介紹如何封裝 axios 請(qǐng)求,具體實(shí)現(xiàn)的功能如下
-
基本配置
配置默認(rèn)請(qǐng)求地址,超時(shí)等
-
請(qǐng)求攔截
攔截 request 請(qǐng)求,處理一些發(fā)送請(qǐng)求之前做的處理,譬如給 header 加 token 等
-
響應(yīng)攔截
統(tǒng)一處理后端返回的錯(cuò)誤
-
全局 loading
為所有請(qǐng)求加上全局 loading(可配置是否啟用)
-
取消重復(fù)請(qǐng)求
當(dāng)同樣的請(qǐng)求還沒(méi)返回結(jié)果再次請(qǐng)求直接取消
基礎(chǔ)配置
這里以 vue3 為例,首先安裝 axios,element-plus
npm i axios element-plus
在 src 下新建 http/request.ts 目錄用于寫我們的封裝邏輯,然后調(diào)用 aixos 的 create 方法寫一些基本配置
import axios, { AxiosResponse, InternalAxiosRequestConfig } from 'axios';
const service = axios.create({
method: 'get',
baseURL: import.meta.env.VITE_APP_API, //.env中的VITE_APP_API參數(shù)
headers: {
'Content-Type': 'application/json;charset=utf-8',
},
timeout: 10000, //超時(shí)時(shí)間
});
export default service;
這樣便完成了 aixos 的基本配置,接下來(lái)我們可以在 http 下新建 api 目錄用于存放我們接口請(qǐng)求,比如在 api 下創(chuàng)建 login.ts 用于寫登錄相關(guān)請(qǐng)求方法,這里的/auth/login
我已經(jīng)用 nestjs 寫好了
import request from './request';
export const login = (data: any) => {
return request({
url: '/auth/login',
data,
method: 'post',
});
};
然后可以在頁(yè)面進(jìn)行調(diào)用
<script lang="ts" setup>
import { login } from '@/http/login';
const loginManage = async () => {
const data = await login({
username: '雞哥哥',
password: '1234',
});
console.log(data);
};
loginManage();
</script>
結(jié)果打印如下
響應(yīng)攔截器
我們可以看到返回的數(shù)據(jù)很多都是我們不需要的,我們需要的只有 data 中的數(shù)據(jù),所以這時(shí)候我們便需要一個(gè)響應(yīng)攔截器進(jìn)行處理,同時(shí)在響應(yīng)攔截器中我們不僅僅簡(jiǎn)單處理這個(gè)問(wèn)題,還需要對(duì)后端返回的狀態(tài)碼進(jìn)行判斷,如果不是正確的狀態(tài)碼可以彈窗提示后端返回的描述(也可以自定義)
service.interceptors.response.use(
(res: AxiosResponse<any, any>) => {
const { data } = res;
if (data.code != 200) {
ElMessage({
message: data.describe,
type: 'error',
});
if (data.code === 401) {
//登錄狀態(tài)已過(guò)期.處理路由重定向
console.log('loginOut');
}
throw new Error(data.describe);
}
return data;
},
(error) => {
let { message } = error;
if (message == 'Network Error') {
message = '后端接口連接異常';
} else if (message.includes('timeout')) {
message = '系統(tǒng)接口請(qǐng)求超時(shí)';
} else if (message.includes('Request failed with status code')) {
message = '系統(tǒng)接口' + message.substr(message.length - 3) + '異常';
}
ElMessage({
message: message,
type: 'error',
});
return Promise.reject(error);
},
);
這里規(guī)定后臺(tái) code 不是 200 的請(qǐng)求是異常的,需要彈出異常信息(當(dāng)然這里由自己規(guī)定),同時(shí) 401 狀態(tài)表示登錄已過(guò)期,如果你需要更多的異常處理都可以寫在這里。注意這里都是對(duì) code 狀態(tài)碼的判斷,這表示后臺(tái)返回的 http 的 status 都是 2xx 才會(huì)進(jìn)入的邏輯判斷,如果后臺(tái)返回 status 異常狀態(tài)碼比如 4xx,3xx 等就會(huì)進(jìn)入 error 里,可以在 error 里進(jìn)行邏輯處理,這里要和后端小朋友約定好
請(qǐng)求攔截器
請(qǐng)求請(qǐng)求攔截器和響應(yīng)攔截器類似,只不過(guò)是在請(qǐng)求發(fā)送之前我們需要做哪些處理,它的用法如下
service.interceptors.request.use(
(config: InternalAxiosRequestConfig<any>) => {
console.log(config);
return config;
},
(error) => {
console.log(error);
},
);
我們可以看到 config 中包含了我們請(qǐng)求的一些信息像 headers,data 等等,我們是可以在這里對(duì)其進(jìn)行修改的,比如我們?cè)?headers 加一個(gè) token
declare module "axios" {
interface InternalAxiosRequestConfig<D = any, T = any> {
isToken?: boolean;
}
}
declare module "axios" {
interface AxiosRequestConfig<D = any> {
isToken?: boolean;
}
}
service.interceptors.request.use(
(config: InternalAxiosRequestConfig<any>) => {
const { isToken = true } = config;
if (localStorage.getItem('token') && !isToken) {
config.headers['Authorization'] =
'Bearer ' + localStorage.getItem('token'); // 讓每個(gè)請(qǐng)求攜帶自定義token 請(qǐng)根據(jù)實(shí)際情況自行修改
}
return config;
},
(error) => {
console.log(error);
},
);
這里假設(shè)用戶登錄成功將 token 緩存到了 localStorage 中,接口是否需要 token 則是在請(qǐng)求的時(shí)候自己配置,比如 login 接口不加 token,注意這里需要給InternalAxiosRequestConfig
和AxiosRequestConfig
加上自定義的字段,否則 TS 會(huì)報(bào)錯(cuò)
export const login = (data: any) => {
return request({
url: '/auth/login',
data,
isToken: false,
method: 'post',
});
};
此時(shí)我們可以獲取到 config 中的 isToken 了
添加全局 loading
我們通常會(huì)在請(qǐng)求開始前加上 loading 彈窗,請(qǐng)求結(jié)束再進(jìn)行關(guān)閉,實(shí)現(xiàn)其實(shí)很簡(jiǎn)單,在請(qǐng)求攔截器中調(diào)用 ElLoading.service 實(shí)例,響應(yīng)攔截器中 close 即可。但是這樣會(huì)出現(xiàn)一個(gè)問(wèn)題,
當(dāng)多個(gè)請(qǐng)求進(jìn)入會(huì)開啟多個(gè) loading 實(shí)例嗎? 這倒不會(huì),因?yàn)樵?element-plus 中的 ElLoading.service()是單例模式,只會(huì)開啟一個(gè) loading。
上述問(wèn)題雖然不需要我們考慮,但是還有一個(gè)問(wèn)題
同時(shí)進(jìn)來(lái)多個(gè)請(qǐng)求,此時(shí) loading 已經(jīng)開啟,假設(shè) 1 秒后多個(gè)請(qǐng)求中其中一個(gè)請(qǐng)求請(qǐng)求完成,按照上述邏輯會(huì)執(zhí)行 close 方法,但是還有請(qǐng)求未完成 loading 卻已經(jīng)關(guān)閉,顯然這不符合我們的期望
因此,我們可以定義一個(gè)變量用于記錄正在請(qǐng)求的數(shù)量,當(dāng)該變量為 1 時(shí)開啟 loading,當(dāng)變量為 0 時(shí)關(guān)閉 loading,同樣的我們還定義了 config 中的 loading 讓開發(fā)者自己決定是否開啟 loading,實(shí)現(xiàn)如下
let requestCount = 0;
const showLoading = () => {
requestCount++;
if (requestCount === 1) loadingInstance();
};
const closeLoading = () => {
requestCount--;
if (requestCount === 0) loadingInstance().close();
};
service.interceptors.request.use(
(config: InternalAxiosRequestConfig<any>) => {
const { loading = true, isToken = true } = config;
return config;
},
(error) => {
console.log(error);
},
);
service.interceptors.response.use(
(res: AxiosResponse<any, any>) => {
const { data, config } = res;
const { loading = true } = config;
if (loading) closeLoading();
},
(error) => {
closeLoading();
return Promise.reject(error);
},
);
取消重復(fù)請(qǐng)求
當(dāng)同樣的請(qǐng)求還沒(méi)返回結(jié)果再次請(qǐng)求我們需要直接取消這個(gè)請(qǐng)求,通常發(fā)生在用戶連續(xù)點(diǎn)擊然后請(qǐng)求接口的情況,但是如果加了 loading 這種情況就不會(huì)發(fā)生。axios 中取消請(qǐng)求可以使用AbortController
,注意這個(gè) api 需要 axios 版本大于 v0.22.0 才可使用,低版本可以使用CancelToken
,下面看一下AbortController
使用方法
service.interceptors.request.use(
(config: InternalAxiosRequestConfig<any>) => {
const controller = new AbortController();
const { loading = true, isToken = true } = config;
config.signal = controller.signal;
controller.abort();
return config;
},
(error) => {
console.log(error);
},
);
這里是將 controller 的 signal 賦值給 config 的 sigal,然后執(zhí)行 controller 的 abort 函數(shù)即可取消請(qǐng)求
知道了如何取消 axios 請(qǐng)求,接下來(lái)我們就可以寫取消重復(fù)請(qǐng)求的邏輯了
當(dāng)攔截到請(qǐng)求的時(shí)候,將 config 中的 data,url 作為 key 值,AbortController 實(shí)例作為 value 存在一個(gè) map 中,判斷是否 key 值是否存在來(lái)決定是取消請(qǐng)求還是保存實(shí)例
const requestMap = new Map();
service.interceptors.request.use(
(config: InternalAxiosRequestConfig<any>) => {
const controller = new AbortController();
const key = config.data + config.url;
config.signal = controller.signal;
if (requestMap.has(key)) {
requestMap.get(key).abort();
requestMap.delete(key);
} else {
requestMap.set(key, controller);
}
return config;
},
(error) => {
console.log(error);
},
);
我們短時(shí)間內(nèi)發(fā)送兩次請(qǐng)求就會(huì)發(fā)現(xiàn)有一個(gè)請(qǐng)求被取消了
到這里基本就完成了 axios 的封裝,下面是完整代碼,直接 CV,就可以摸魚一整天~文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-471634.html
import axios, {
AxiosInstance,
AxiosResponse,
InternalAxiosRequestConfig,
} from "axios";
import { ElMessage, ElLoading } from "element-plus";
const loadingInstance = ElLoading.service;
let requestCount = 0;
const showLoading = () => {
requestCount++;
if (requestCount === 1) loadingInstance();
};
const closeLoading = () => {
requestCount--;
if (requestCount === 0) loadingInstance().close();
};
const service: AxiosInstance = axios.create({
method: "get",
baseURL: import.meta.env.VITE_APP_API,
headers: {
"Content-Type": "application/json;charset=utf-8",
},
timeout: 10000,
});
//請(qǐng)求攔截
declare module "axios" {
interface InternalAxiosRequestConfig<D = any, T = any> {
loading?: boolean;
isToken?: boolean;
}
}
declare module "axios" {
interface AxiosRequestConfig<D = any> {
loading?: boolean;
isToken?: boolean;
}
}
const requestMap = new Map();
service.interceptors.request.use(
(config: InternalAxiosRequestConfig<any>) => {
const controller = new AbortController();
const key = config.data + config.url;
config.signal = controller.signal;
if (requestMap.has(key)) {
requestMap.get(key).abort();
requestMap.delete(key);
} else {
requestMap.set(key, controller);
}
console.log(123);
const { loading = true, isToken = true } = config;
if (loading) showLoading();
if (localStorage.getItem("token") && !isToken) {
config.headers["Authorization"] =
"Bearer " + localStorage.getItem("token"); // 讓每個(gè)請(qǐng)求攜帶自定義token 請(qǐng)根據(jù)實(shí)際情況自行修改
}
return config;
},
(error) => {
console.log(error);
}
);
service.interceptors.response.use(
(res: AxiosResponse<any, any>) => {
const { data, config } = res;
const { loading = true } = config;
if (loading) closeLoading();
if (data.code != 200) {
ElMessage({
message: data.describe,
type: "error",
});
if (data.code === 401) {
//登錄狀態(tài)已過(guò)期.處理路由重定向
console.log("loginOut");
}
throw new Error(data.describe);
}
return data;
},
(error) => {
closeLoading();
let { message } = error;
if (message == "Network Error") {
message = "后端接口連接異常";
} else if (message.includes("timeout")) {
message = "系統(tǒng)接口請(qǐng)求超時(shí)";
} else if (message.includes("Request failed with status code")) {
message = "系統(tǒng)接口" + message.substr(message.length - 3) + "異常";
}
ElMessage({
message: message,
type: "error",
});
return Promise.reject(error);
}
);
export default service;
微信掃碼關(guān)注公眾號(hào)web前端進(jìn)階每日更新最新前端技術(shù)文章,你想要的都有!文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-471634.html
到了這里,關(guān)于一篇文章帶你詳細(xì)了解axios的封裝的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!