共计 9130 个字符,预计需要花费 23 分钟才能阅读完成。
前言
最近在写 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 title
VITE_APP_Title=Vue3 Basic Admin Dev
# baseUrl
VITE_BASE_API= /dev
# public path
VITE_PUBLIC_PATH = /
# production
# app title
VITE_APP_Title=Vue3 Basic Admin
# baseUrl
VITE_BASE_API= /prod
# public path
VITE_PUBLIC_PATH = /
# test
# app title
VITE_APP_Title=Vue3 Basic Admin Test
# baseUrl
VITE_BASE_API= /test
# public path
VITE_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
等支流技术开发,内置许多开箱即用的组件,能疾速构建中后盾管理系统,目前决定齐全开源。点击预览