前言
最近在写admin
我的项目时,想对axios
方面进行一个彻底的重造,除了惯例的错误信息拦挡外,减少一些新的性能,目前已实现:loading加载、谬误主动重试、谬误日志记录、勾销反复申请,两头也遇到过一些问题,这里记录下如何解决的,心愿对你有所帮忙。
ps:这里应用的vue3+ts+vite
根底配置
先装置axios:
# 抉择一个你喜爱的包管理器# NPM$ npm install axios -s# Yarn$ yarn add axios# pnpm$ pnpm install axios -s
初始化axios
import type { AxiosError, AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios";import axios from "axios";const service: AxiosInstance = axios.create({ baseURL: '/api', timeout: 10 * 1000, // 申请超时工夫 headers: { "Content-Type": "application/json;charset=UTF-8" }});
辨别不同环境
理论我的项目中,咱们可能分开发环境、测试环境和生产环境,所以咱们先在根目录建设三个文件:.env.development
,.env.production
, .env.test
其中 vite
内置了几个办法获取环境变量:
如果你想自定义一些变量,必须要以 VITE_
结尾,这样才会被vite检测到并读取,例如咱们能够在刚刚建设三种环境下定义title
,api
等字段,vite
会主动依据以后的环境去加载对应的文件。
# development# app titleVITE_APP_Title=Vue3 Basic Admin Dev# baseUrlVITE_BASE_API= /dev# public pathVITE_PUBLIC_PATH = /
# production# app titleVITE_APP_Title=Vue3 Basic Admin# baseUrlVITE_BASE_API= /prod# public pathVITE_PUBLIC_PATH = /
# test# app titleVITE_APP_Title=Vue3 Basic Admin Test# baseUrlVITE_BASE_API= /test# public pathVITE_PUBLIC_PATH = /
批改axios baseUrl:
import type { AxiosError, AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios";import axios from "axios";const service: AxiosInstance = axios.create({ baseURL: import.meta.env.VITE_BASE_API, timeout: 10 * 1000, // 申请超时工夫 headers: { "Content-Type": "application/json;charset=UTF-8" }});
抽取hook
有些时候咱们不心愿我的项目中间接应用 import.meta.env
去读取env
配置,这样可能会不便于管理,这个时候能够抽取一个公共hook
文件,这个hook
文件次要就是去读取env
配置,而后页面中再通过读取这个hook
拿到对应的配置。
还有一个起因就是 如果env
配置中文件名字变换了,还得一个个的去我的项目中手动改,比拟麻烦,在hook
中能够解构别名,而后return
进来即可。
建设 src/hooks/useEnv.ts:
export function useEnv() { const { VITE_APP_TITLE, VITE_BASE_API, VITE_PUBLIC_PATH, MODE } = import.meta.env; // 如果名字变换了,咱们能够在这里解构别名 return { MODE, VITE_APP_NAME, VITE_BASE_API, VITE_PUBLIC_PATH, VITE_BASE_UPLOAD_API };}
再更改axios
import type { AxiosError, AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios";import axios from "axios";import { useEnv } from "@/hooks";const { VITE_BASE_API } = useEnv();const service: AxiosInstance = axios.create({ baseURL: VITE_BASE_API, timeout: 10 * 1000, // 申请超时工夫 headers: { "Content-Type": "application/json;charset=UTF-8" }});
响应和拦挡
这里就是陈词滥调的axios申请和相应了,相干的例子有太多了,这里就简略形容下。
# 申请service.interceptors.request.use((config: AxiosRequestConfig) => { // 这里能够设置token: config!.headers!.Authorization = token return config;});
# 响应service.interceptors.response.use((response: AxiosResponse) => { const data = response.data; if (data.code === 200) { return data; } else { return Promise.reject(data); } }, (err) => { return Promise.reject(err.response); });
对外裸露办法和应用
内部应用时想通过xx.ge
t间接能申请,所以这里定义一个对外裸露的办法。
// ...后面的省略const request = { get<T = any>(url: string, data?: any): Promise<T> { return request.request("GET", url, { params: data }); }, post<T = any>(url: string, data?: any): Promise<T> { return request.request("POST", url, { data }); }, put<T = any>(url: string, data?: any): Promise<T> { return request.request("PUT", url, { data }); }, delete<T = any>(url: string, data?: any): Promise<T> { return request.request("DELETE", url, { params: data }); }, request<T = any>(method = "GET", url: string, data?: any): Promise<T> { return new Promise((resolve, reject) => { service({ method, url, ...data }) .then((res) => { resolve(res as unknown as Promise<T>); }) .catch((e: Error | AxiosError) => { reject(e); }) }); }};export default request;
内部应用:
api/user.ts文件中:
import request from "@/utils/request";export const login = (data?: any) => request.post('/login', data);
到这里根底封装曾经实现,接下来咱们给axios增加一些性能。
谬误日志收集
需解决的问题:
- 收集接口错误信息
所以为了解决这个问题,咱们能够给axios
扩大一个log
办法,去解决这个问题。
首先,先在axios
同级定义log.ts
文件:定义addErrorLog
办法
- 当页面进入响应拦截器的且进入
error
回调的时候,获取以后的url
,method
,params
,data
等参数,并申请增加日志接口。
export const addAjaxErrorLog = (err: any) => { const { url, method, params, data, requestOptions } = err.config; addErrorLog({ type:'ajax', url: <string>url, method, params: ["get", "delete"].includes(<string>method) ? JSON.stringify(params) : JSON.stringify(data), data: err.data ? JSON.stringify(err.data) : "", detail: JSON.stringify(err) });};
axios中引入应用
import {addAjaxErrorLog} from "./log"# 响应service.interceptors.response.use((response: AxiosResponse) => { // ...其余代码 }, (err) => { // ...其余代码 addAjaxErrorLog(err.response) });
成果展现:
增加loading性能
需解决的问题:
- 有时候咱们可能须要
axios
申请时主动开启loading
,完结时敞开loading; - 初始化同时申请
n
个接口,申请实现后敞开loading
,但又不想页面中应用Primise.all
所以为了解决这两个问题,咱们能够给axios
扩大一个loading
办法,去解决这两个问题。
首先,先在axios
同级定义loading.ts
文件:定义init
、add
和close
三个办法
- 当进入申请拦截器的时候,调用
addLoading
,开启loading
并loadingCount++
- 当页面进入响应拦截器的时候,调用
closeLoaidng
,loadingCount--
,如果loadingCount
数量为0
则敞开loading
import { ElLoading } from 'element-plus'export class AxiosLoading { loadingCount: number; loading:any constructor() { this.loadingCount = 0; } initLoading(){ if(this.loading){ this.loading?.close?.() } this.loading=ElLoading.service({ fullscreen:true }) } addLoading() { if (this.loadingCount === 0) { initLoading() } this.loadingCount++; } closeLoading() { if (this.loadingCount > 0) { if (this.loadingCount === 1) { loading.close(); } this.loadingCount--; } }}
axios中引入应用:
import {AxiosLoaing} from "./loading"const axiosLoaing=new AxiosLoaing()// ...其余代码# 申请service.interceptors.request.use((config: AxiosRequestConfig) => { axiosLoaing.addLoading(); return config;});
# 响应service.interceptors.response.use((response: AxiosResponse) => { // ...其余代码 axiosLoaing.closeLoading(); }, (err) => { // ...其余代码 axiosLoaing.closeLoading(); });
成果展现:
勾销反复申请
需解决的问题:
- 有时候可能会同时申请雷同的接口(例如:分页的时候很快的切换几次页码),如果该接口比较慢就比拟节约性能
所以为了解决这个问题,咱们能够给axios
扩大一个calcel
办法,去解决这个问题。
这里先理解下axios
给出的示例,申请接口的时候通过AbortController
拿到实例,申请时携带signal
,如果想勾销能够通过返回实例的abort
办法。
首先,先在axios
同级定义calcel.ts
文件:定义addPending
和removePending
,removeAllPending
,reset
四个办法
- 当进入申请拦截器的时候,调用
addPending
,调用addPending
前,先执行removePedding
办法,终止掉申请队列中的申请,pendingMap set
以后申请,以method,url
参数当做key
,value
为AbortController
实例; - 当页面进入响应拦截器的时候,调用
remove
,革除pendingMap
中的以后key
- 须要留神的是,勾销申请后会触发响应拦截器的
err
回调,所以须要做一下解决。
import type { AxiosRequestConfig } from "axios";export class AxiosCancel { pendingMap: Map<string, AbortController>; constructor() { this.pendingMap = new Map<string, AbortController>(); } generateKey(config: AxiosRequestConfig): string { const { method, url } = config; return [ url || "", method || ""].join("&"); } addPending(config: AxiosRequestConfig) { this.removePending(config); const key: string = this.generateKey(config); if (!this.pendingMap.has(key)) { const controller = new AbortController(); config.signal = controller.signal; this.pendingMap.set(key, controller); } else { config.signal = (this.pendingMap.get(key) as AbortController).signal; } } removePending(config: AxiosRequestConfig) { const key: string = this.generateKey(config); if (this.pendingMap.has(key)) { (this.pendingMap.get(key) as AbortController).abort(); this.pendingMap.delete(key); } } removeAllPending() { this.pendingMap.forEach((cancel: AbortController) => { cancel.abort(); }); this.reset(); } reset() { this.pendingMap = new Map<string, AbortController>(); }}
axios中引入应用:
import {AxiosCancel} from "./cancel"const axiosCancel=new AxiosCancel()// ...其余代码# 申请service.interceptors.request.use((config: AxiosRequestConfig) => { axiosCancel.addPending(); return config;});
# 响应service.interceptors.response.use((response: AxiosResponse) => { // ...其余代码 axiosCancel.removePending(); }, (err) => { if (err.code === "ERR_CANCELED") return; // ...其余代码 axiosCancel.removePending(); });
成果展现:
谬误重试机制
需解决的问题:
- 有时候接口可能忽然出问题,所以容许谬误主动重试 (慎用!!!)
所以为了解决这个问题,咱们能够给axios
扩大一个retry
办法,去解决这个问题。
首先,先在axios
同级定义retry.ts
文件:定义retry
办法
- 当页面进入响应拦截器的且进入
error
回调的时候,判断以后接口的目前的重试次数是否大于规定的重试次数,如果小于,则执行retry
办法进行接口从新申请。
import type { AxiosError, AxiosInstance } from "axios";export class AxiosRetry { retry(service: AxiosInstance, err: AxiosError) { const config = err?.config as any; config._retryCount = config._retryCount || 0; config._retryCount += 1; delete config.headers; //删除config中的header,采纳默认生成的header setTimeout(() => { service(config); }, 100); }}
axios中引入应用:
import {AxiosRetry} from "./retry"
响应
service.interceptors.response.use((response: AxiosResponse) => { // ...其余代码 }, (err) => { if ((err.config._retryCount || 0) < 3) { const axiosRetry = new AxiosRetry(); axiosRetry.retry(service, err); return; } // ...其余代码 });
成果展现:
性能配置
需解决的问题:
- 有时候可能某个接口仅须要局部性能,例如仅某个接口须要重试,其余的不须要的状况。
所以为了解决这个问题,咱们能够给axios
减少了一个默认配置axiosOptions
,去解决这个问题。
- 当页面进入须要应用某些参数的时候,先去读以后接口是否传递了,如果没有则去读取
axios
默认配置。
设置默认配置:
interface axiosConfig { successMessage?: boolean; // 是否提醒胜利信息 errorMessage?: boolean; // 是否提醒失败信息 cancelSame?: boolean; // 是否勾销雷同接口 retryCount?: number; // 失败重试次数 isRetry?: boolean; // 是否失败重试}const defaultConfig: axiosConfig = { successMessage: false, errorMessage: true, cancelSame: false, isRetry: false, retryCount: 3};
批改request,加上requestOptions参数:
# 批改request办法const request = { request<T = any>(method = "GET", url: string, data?: any, config?: axiosConfig): Promise<T> { // 和默认配置合并 const options = Object.assign({}, defaultConfig, config); return new Promise((resolve, reject) => { service({ method, url, ...data, requestOptions: options }) .then((res) => { // ...其余代码 }) .catch((e: Error | AxiosError) => { // ...其余代码 }) }); }};
申请拦截器:
service.interceptors.request.use((config: AxiosRequestConfig) => { const { cancelSame } = config.requestOptions; if (cancelSame) { axiosCancel.addPending(config); } axiosLoading.addLoading(); return config;});
响应拦截器:
service.interceptors.response.use((response: AxiosResponse) => { const { cancelSame } = response.config.requestOptions; if (cancelSame) { axiosCancel.removePending(response.config); } // ...其余代码 }, (err) => { const { isRetry, retryCount,cancelSame } = err.config.requestOptions; if (isRetry && (err.config._retryCount || 0) < retryCount) { //...其余代码 } cancelSame && axiosCancel.removePending(err.config || {}); // ...其余代码 });
应用:
export const sameTestApi = (data?: any) => request.get('/test', data, { cancelSame: true });
成果展现:
(?)
最初
到这里axios集成曾经实现了,残缺代码寄存在vue3-basic-admin外面,vue3-basic-admin
是一款开源开箱即用的中后盾管理系统。基于 Vue3
、Vite
、Element-Plus
、TypeScript
、Pinia
等支流技术开发,内置许多开箱即用的组件,能疾速构建中后盾管理系统,目前决定齐全开源。点击预览