前景提要:
ts 简易封装 axios,统一 API
axios 很多额外功能都是基于拦截器实现。有些功能想要全局使用,因此将拦截器注册在全局。比如重复请求过滤。但也有一小部分请求不希望进行过滤,比如并发上传文件。
因此希望可以在具体的请求方法上,通过配置 config 从而决定针对当前请求,某个拦截器是否启用。
注意:在具体方法上配置时,没有明确配置为 true 时,可能没有开启功能。因为在实例化时的 config 对象上,可能将该功能设为 false 了。整个 axios 实例上关闭了该功能,则此时具体方法上必须明确设为 true 覆盖掉实例化的配置才能启用。
以当前请求是否开启响应反馈为例:
// index.ts
import HttpRequest from "./http/request";
// 实例化 axios
const httpRequest = new HttpRequest({
baseURL: import.meta.env.VITE_APP_API_BASE_URL,
timeout: import.meta.env.VITE_APP_API_TIMEOUT,
// 开启响应反馈拦截器功能(注册拦截器后,默认就是开启)
showMessage: true
});
// 在该 axios 实例上注册请求响应反馈拦截器
httpRequest.getInstance().interceptors.response.use(responseMessageOnFulfilled, null);
// 默认开启当前请求的请求响应弹窗反馈
async function handleMessageOpen() {
const res = await httpRequest.get({
url: "/mock/test"
});
console.log("res", res);
}
// 手动关闭当前请求的响应反馈
async function handleMessageClose() {
const res = await httpRequest.get({
url: "/mock/test",
showMessage: false
});
console.log("res", res);
}
给 axios config 对象添加额外配置项,首先得扩充它的类型AxiosRequestConfig
:
// 开关的默认值(默认都是开启)
const DEFAULT_EXTRA_FEATURE_CONFIG = { showMessage: true };
/** 扩展 axios 的请求配置类型 */
export interface MyAxiosRequestConfig<TReqBodyData = any> extends AxiosRequestConfig<TReqBodyData> {
showMessage?: boolean; // 是否开启请求反馈提示框
}
拦截器中也需要用到 config 对象。但是在最新的 axios 中,请求拦截器中 config 的类型不再是 AxiosRequestConfig
,而是InternalAxiosRequestConfig
。因此还需要扩展一下请求拦截器 configInternalAxiosRequestConfig
类型。
/** 给拦截器使用 */
export interface MyInternalAxiosRequestConfig extends InternalAxiosRequestConfig {
showMessage?: boolean;
}
有些功能在响应拦截器 onFulfilled 回调中完成,如响应反馈,也有些在 onRejected 回调中完成,如请求重试。因此也需要扩展响应拦截器中的回调,AxiosResponse
和AxiosError
里的 config 对象类型。
export interface MyAxiosResponse extends AxiosResponse {
config: MyInternalAxiosRequestConfig;
}
export interface MyAxiosError extends AxiosError {
config: MyInternalAxiosRequestConfig;
}
实现各种功能的拦截器,可能是请求拦截器,也可能是响应拦截器。请求拦截器中可以很轻易拿到具体方法 config 上填写的配置项。但是响应拦截器却不好拿,因为 AxiosResponse 实例上的 config 对象是新生成的,它没有我们额外补充的配置项。
因此,我们需要在请求拦截器中先手动保存用户填写的配置项的值,然后在响应拦截器中手动添加到 config 对象上。并且该响应拦截器必须是最先执行的响应拦截器。
class HttpRequest {
private readonly instance: AxiosInstance;
private readonly extraConfig: Record<string, boolean> = DEFAULT_EXTRA_FEATURE_CONFIG;
constructor(config: MyAxiosRequestConfig) {
this.instance = axios.create(config);
// 记录 config 额外配置项
this.instance.interceptors.request.use((config: MyInternalAxiosRequestConfig) => {
Object.keys(this.extraConfig).forEach(item => {
if (config[item as keyof MyInternalAxiosRequestConfig] === false)
this.extraConfig[item] = !!config[item as keyof MyInternalAxiosRequestConfig];
});
return config;
});
// 将配置项补充到响应的 config 对象上
this.instance.interceptors.response.use(
(res: any) => {
Object.keys(this.extraConfig).forEach(item => {
res.config[item] = this.extraConfig[item];
this.extraConfig[item] = true; // 配置复原成默认开启
});
return res;
},
(err: any) => {
Object.keys(this.extraConfig).forEach(item => {
err.config[item] = this.extraConfig[item];
this.extraConfig[item] = true; // 配置复原成默认开启
});
throw err;
}
);
}
...
}
// src\api\http\message.ts
import { MyAxiosResponse } from "./request";
// 假设接口:{code: number, data: any, msg: string}
function showMessage(res: MyAxiosResponse) {
const { data } = res;
// alert 消息提示
if (data.code >= 200 && data.code < 300) alert(data.msg);
return res;
}
/**
* 响应业务消息提示
*/
export function responseMessageOnFulfilled(res: MyAxiosResponse) {
if (res.config.showMessage) showMessage(res); // showMessage 为 true,才给出反馈
return res;
}
通过在 config 对象上添加额外配置的方式来实现针对某个请求开关某个拦截器的目标。