一文帶你詳細(xì)了解vue axios的封裝
axios 封裝
對請求的封裝在實(shí)際項(xiàng)目中是十分必要的,它可以讓我們統(tǒng)一處理 http 請求。比如做一些攔截,處理一些錯(cuò)誤等。本篇文章將詳細(xì)介紹如何封裝 axios 請求,具體實(shí)現(xiàn)的功能如下
基本配置
配置默認(rèn)請求地址,超時(shí)等
請求攔截
攔截 request 請求,處理一些發(fā)送請求之前做的處理,譬如給 header 加 token 等
響應(yīng)攔截
統(tǒng)一處理后端返回的錯(cuò)誤
全局 loading
為所有請求加上全局 loading(可配置是否啟用)
取消重復(fù)請求
當(dāng)同樣的請求還沒返回結(jié)果再次請求直接取消
基礎(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 的基本配置,接下來我們可以在 http 下新建 api 目錄用于存放我們接口請求,比如在 api 下創(chuàng)建 login.ts 用于寫登錄相關(guān)請求方法,這里的/auth/login
我已經(jīng)用 nestjs 寫好了
import request from './request'; export const login = (data: any) => { return request({ url: '/auth/login', data, method: 'post', }); };
然后可以在頁面進(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)攔截器中我們不僅僅簡單處理這個(gè)問題,還需要對后端返回的狀態(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)已過期.處理路由重定向 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)接口請求超時(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ī)定后臺 code 不是 200 的請求是異常的,需要彈出異常信息(當(dāng)然這里由自己規(guī)定),同時(shí) 401 狀態(tài)表示登錄已過期,如果你需要更多的異常處理都可以寫在這里。注意這里都是對 code 狀態(tài)碼的判斷,這表示后臺返回的 http 的 status 都是 2xx 才會進(jìn)入的邏輯判斷,如果后臺返回 status 異常狀態(tài)碼比如 4xx,3xx 等就會進(jìn)入 error 里,可以在 error 里進(jìn)行邏輯處理,這里要和后端小朋友約定好
請求攔截器
請求請求攔截器和響應(yīng)攔截器類似,只不過是在請求發(fā)送之前我們需要做哪些處理,它的用法如下
service.interceptors.request.use( (config: InternalAxiosRequestConfig<any>) => { console.log(config); return config; }, (error) => { console.log(error); }, );
我們可以看到 config 中包含了我們請求的一些信息像 headers,data 等等,我們是可以在這里對其進(jìn)行修改的,比如我們在 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è)請求攜帶自定義token 請根據(jù)實(shí)際情況自行修改 } return config; }, (error) => { console.log(error); }, );
這里假設(shè)用戶登錄成功將 token 緩存到了 localStorage 中,接口是否需要 token 則是在請求的時(shí)候自己配置,比如 login 接口不加 token,注意這里需要給InternalAxiosRequestConfig
和AxiosRequestConfig
加上自定義的字段,否則 TS 會報(bào)錯(cuò)
export const login = (data: any) => { return request({ url: '/auth/login', data, isToken: false, method: 'post', }); };
此時(shí)我們可以獲取到 config 中的 isToken 了
添加全局 loading
我們通常會在請求開始前加上 loading 彈窗,請求結(jié)束再進(jìn)行關(guān)閉,實(shí)現(xiàn)其實(shí)很簡單,在請求攔截器中調(diào)用 ElLoading.service 實(shí)例,響應(yīng)攔截器中 close 即可。但是這樣會出現(xiàn)一個(gè)問題,
當(dāng)多個(gè)請求進(jìn)入會開啟多個(gè) loading 實(shí)例嗎? 這倒不會,因?yàn)樵?element-plus 中的 ElLoading.service()是單例模式,只會開啟一個(gè) loading。
上述問題雖然不需要我們考慮,但是還有一個(gè)問題
同時(shí)進(jìn)來多個(gè)請求,此時(shí) loading 已經(jīng)開啟,假設(shè) 1 秒后多個(gè)請求中其中一個(gè)請求請求完成,按照上述邏輯會執(zhí)行 close 方法,但是還有請求未完成 loading 卻已經(jīng)關(guān)閉,顯然這不符合我們的期望
因此,我們可以定義一個(gè)變量用于記錄正在請求的數(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ù)請求
當(dāng)同樣的請求還沒返回結(jié)果再次請求我們需要直接取消這個(gè)請求,通常發(fā)生在用戶連續(xù)點(diǎn)擊然后請求接口的情況,但是如果加了 loading 這種情況就不會發(fā)生。axios 中取消請求可以使用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ù)即可取消請求
知道了如何取消 axios 請求,接下來我們就可以寫取消重復(fù)請求的邏輯了
當(dāng)攔截到請求的時(shí)候,將 config 中的 data,url 作為 key 值,AbortController 實(shí)例作為 value 存在一個(gè) map 中,判斷是否 key 值是否存在來決定是取消請求還是保存實(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ā)送兩次請求就會發(fā)現(xiàn)有一個(gè)請求被取消了
到這里基本就完成了 axios 的封裝,下面是完整代碼,直接 CV,就可以摸魚一整天~
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, }); //請求攔截 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è)請求攜帶自定義token 請根據(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)已過期.處理路由重定向 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)接口請求超時(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;
以上就是一文帶你詳細(xì)了解vue axios的封裝的詳細(xì)內(nèi)容,更多關(guān)于vue axios的封裝的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
使用element-ui的Pagination分頁的注意事項(xiàng)及說明
這篇文章主要介紹了使用element-ui的Pagination分頁的注意事項(xiàng)及說明,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-02-02Vue路由組件的緩存keep-alive和include屬性的具體使用
:瀏覽器頁面在進(jìn)行切換時(shí),原有的路由組件會被銷毀,通過緩存可以保存被切換的路由組件,本文主要介紹了Vue路由組件的緩存keep-alive和include屬性的具體使用,感興趣的可以了解一下2023-11-11Vue記住滾動條和實(shí)現(xiàn)下拉加載的完美方法
這篇文章主要給大家介紹了關(guān)于Vue記住滾動條和實(shí)現(xiàn)下拉加載的完美方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07vue動態(tài)顯示圖片報(bào)錯(cuò)404的解決
這篇文章主要介紹了vue動態(tài)顯示圖片報(bào)錯(cuò)404的解決,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-07-07WebStorm啟動vue項(xiàng)目報(bào)錯(cuò)代碼:1080?throw?err解決辦法
在使用webstorm新建vue項(xiàng)目時(shí)常會遇到一些報(bào)錯(cuò),下面這篇文章主要給大家介紹了關(guān)于WebStorm啟動vue項(xiàng)目報(bào)錯(cuò)代碼:1080?throw?err的解決辦法,文中將解決辦法介紹的非常詳細(xì),需要的朋友可以參考下2023-12-12瀏覽器事件循環(huán)與vue nextTicket的實(shí)現(xiàn)
這篇文章主要介紹了瀏覽器事件循環(huán)(結(jié)合vue nextTicket)的實(shí)現(xiàn)方法,需要的朋友可以參考下2019-04-04Vue2.0中三種常用傳值方式(父傳子、子傳父、非父子組件傳值)
在Vue的框架開發(fā)的項(xiàng)目過程中,經(jīng)常會用到組件來管理不同的功能,有一些公共的組件會被提取出來。下面通過本文給大家介紹Vue開發(fā)中常用的三種傳值方式父傳子、子傳父、非父子組件傳值,需要的朋友參考下吧2018-08-08Vue3監(jiān)聽store中數(shù)據(jù)變化的三種方式
這篇文章給大家介紹了Vue3監(jiān)聽store中數(shù)據(jù)變化的三種方法,使用watch和storeToRefs函數(shù),使用計(jì)算屬性computed和使用watchEffect函數(shù)這三種方法,文中通過代碼講解非常詳細(xì),需要的朋友可以參考下2024-01-01使用echarts點(diǎn)擊按鈕從新渲染圖表并更新數(shù)據(jù)
這篇文章主要介紹了使用echarts點(diǎn)擊按鈕從新渲染圖表并更新數(shù)據(jù)方式,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-10-10