我在网上看见很多版本的ts封装axios,各有特色,那么今天就看看我们到底适合什么样的axios封装,我会将我遇到的需求考虑进去,希望给你们带来帮助,同时欢迎指正
在下面我只是写大概封装的流程,有很多业务逻辑没有写出来,主要是针对类型上的处理
一. 使用class类封装
// * http/axios.ts
// axios基本配置
const config = {
baseURL: "/api", //看自己的代理对象
timeout: 15000, // 请求超时时间
withCredentials: true, // 跨域时候允许携带凭证
validateStatus: (status: number) => {
return status >= 200 && status <= 500;
}
};
//为了方便我将所需要的类型定义写在这里
interface Result<T = any> {
code: number;
msg: string;
data: T;
}
// [Omit,Partial,Record](https://www.typescriptlang.org/docs/handbook/utility-types.html#partialtype)不懂的可以去官网了解
type Params = Partial<Record<string, unknown>>;//传入的参数
type Config = Omit<AxiosRequestConfig, "url" | "method">;
class Axios {
//axios 实列
private instance: AxiosInstance;
//锁
private lock: number = 0;
constructor(config: AxiosRequestConfig) {
//创建实例,挂载拦截器
this.instance = axios.create(config);
this.interceptorsRequest();
this.interceptorsResponse();
}
// * 请求拦截
protected interceptorsRequest() {
this.instance.interceptors.request.use((config: AxiosRequestConfig) => {
const headers = config.headers;
// ... 忽略部分代码方便阅读
// 在headers中添加属性和值
// (config.headers as AxiosRequestHeaders)["XXX-XXX"] = "XXX"
// 我这里判断token是否存在及是否需要token,因为在某些情况下不需要传token,但是又有token,虽然很鸡肋,但是没办法
if (getToken() && !headers?.isToken) {
(config.headers as AxiosRequestHeaders)["Blade-Auth"] = "自己的token"
}
return config;
},
(error: AxiosError) => {
return Promise.reject(error);
});
}
// * 响应拦截
protected interceptorsResponse() {
this.instance.interceptors.response.use((response: AxiosResponse) => {
const { config, status: code, data } = response;
//...忽略部分代码
//下面data中的需要根据自己后端返回的数据自行更改
const status = data.code || code;//防止data.code没有值,一般不会
const message = data.msg || data.error_description || "未知错误";
//单独将401的状态抽出来判断,退出登录之类的逻辑书写
if (status === 401) {
//这里为什么要加个锁,因为一个页面可能会有很多请求,请求全部为401,那么都会进来,执行退出登录的逻辑,虽然我在路由切换的时候去取消了正在执行的请求,但难免它会在前面执行,
if (this.lock === 1) return false;
this.lock === 0 && ElMessage.error(message);
this.lock = 1;
//...忽略部分业务代码
return Promise.reject(data);
} else if (status !== 200) {
// 统一处理,状态不为200的,这里的状态是后端自定义抛出的,不是请求的状态
ElMessage.error(message);
return Promise.reject(data);
}
this.lock = 0;
return data;
},
(error: AxiosError) => {
//根据自己业务处理异常状态
ElMessage.error(error.message);
return Promise.reject(error);
});
}
// * 请求方法
// 重写请求方法让返回的类型不是any,有很多人在这里封装的时候就会在里面包一个promise,这个大可不必,它自己本身返回的就是个promise
/**
下面的传入泛型<T = any, R = Result<T>>,返回的是Promise<R>
我们先看需求:
1.一般我们从后端拿到的数据是在response.data中,这个data中的数据格式一般是固定的
{code: number;msg: string;data: any;},反正差不多对吧,而这里面data才是我们真正需要的,
然后我就定义一个Result的interface,这样我就不用每次去写Result了,如下
上面说了他本身返回的就是一个promise,所以需要用Promise包裹一下
request<T>(config: AxiosRequestConfig): Promise<Result<T>> {
return this.instance.request(config);
}
这里和下面的不一样啊,怎么回事勒?来继续
2.有时候后端返回的数据不按常理出牌怎么办?在返回的data中不是我们写的固定格式
{code,msg,data},而是直接返回了固定格式中的data对象,没有code,msg,data,给你的
就是一个对象,此时此刻,心里开始....
当传入一个泛型时T,返回的是有固定格式包裹的类型;R是Result<T>,所以在Promise中的R就是默认的Result<T>;
当第二个泛型参数传入时也就是R,如果R有类型传入,则R就是不默认的Result<T>,而是传入的R类型,在Promise中直接返回的R就没有Result包裹;
这样我们就可以灵活控制返回的类型格式,当然我们也可以将Result写在api.ts中,这样的话如果大部分接口
都实固定格式的话,就要写很多,这个看自己喜欢什么样的方式
request<T = any, R = Result<T>>(config: AxiosRequestConfig): Promise<Result<T>> {
return this.instance.request(config);
}
//下面的get,post等等都差不多,个人比较喜欢用request这一个就行了,这个也看自己
*/
request<T = any, R = Result<T>>(config: AxiosRequestConfig): Promise<R> {
return this.instance.request(config);
}
get<T = any, R = Result<T>>(url: string, params?: Params, config?: Config): Promise<R> {
return this.instance.get(url, { params, ...config });
}
post<T = any, R = Result<T>>(url: string, params?: Params, config?: Config): Promise<R> {
return this.instance.post(url, params, config);
}
put<T = any, R = Result<T>>(url: string, params?: Params, config?: Config): Promise<R> {
return this.instance.put(url, params, config);
}
delete<T = any, R = Result<T>>(url: string, params?: Params, config?: Config): Promise<R> {
return this.instance.delete(url, { params, ...config });
}
}
export default new Axios(config);
定义api,说到这个有的人比较喜欢将api定义到class中统一调用,这样的好处是用一个实列就可以取出所有的api,不用去写一排 import { xxx, xxx } from "api";甚至更多,class中统一调用这个看起来很爽,但是不利于tree-shaking,一般自己是很反感这样的操作
// api.ts
import axios from "http/axios";
interface ImageCode {
key: string;
image: string;
}
// 这里我针对上面写了一个例子
// 1. 这个返回的就是{key: string; image: string;}
export const GetCaptcha = () => axios.request<never, ImageCode>({url: "/blade-auth/oauth/captcha"});
// 2. 这个返回的就是{ code: number; msg: string;data:{key: string; image: string;}}
export const GetCaptcha1 = () => axios.request<ImageCode>({url: "/blade-auth/oauth/captcha"});
// 固定的格式可以根据自己的需求更改,这样我们就可以在api中愉快的玩耍了
二. 不适用类封装axios如何修改默认的返回类型AxiosResponse<T>
基本操作和上面的class类封装没什么区别,区别在于这里没有类
// axios.ts
import axios, { AxiosError, AxiosRequestConfig, AxiosResponse } from "axios";
import { ElMessage } from "element-plus";
const instance = axios.create({
baseURL: "/api",
timeout: 15000, // 请求超时时间
withCredentials: true, // 跨域时候允许携带凭证
validateStatus: (status: number) => {
return status >= 200 && status <= 500;
}
});
// 这里的拦截和 class 类封装的是一样的操作,这里我就不再写一次了
instance.interceptors.request.use(
(config: AxiosRequestConfig) => {
//书写自己的逻辑
return config;
},
(error: AxiosError) => {
return Promise.reject(error);
}
);
instance.interceptors.response.use(
(response: AxiosResponse) => {
const { data } = response;
//书写自己的逻辑
return data;
},
(error: AxiosError) => {
ElMessage.error(error.message);
return Promise.reject(error);
}
);
export default instance;
如果这样去写api的时候返回的就是AxiosResponse<T>,而我们在响应拦截的时候返回的没有response,只返回了response中的data
为什么返回的是AxiosResponse<T>呢?那么要怎么做勒?下图是axios自己的.d.ts文件,我们只需要再定义一个不就可以了
新建axios.d.ts文件,键入下面代码,就可以愉快的玩耍了
import type { Axios } from "axios";
declare module "axios" {
declare interface AxiosInstance extends Axios {
request<T = any, R = Result<T>>(config: AxiosRequestConfig): Promise<R>;
get<T = any, R = Result<T>>(url: string, config?: AxiosRequestConfig): Promise<R>;
delete<T = any, R = Result<T>>(url: string, config?: AxiosRequestConfig): Promise<R>;
head<T = any, R = Result<T>>(url: string, config?: AxiosRequestConfig): Promise<R>;
options<T = any, R = Result<T>>(url: string, config?: AxiosRequestConfig): Promise<R>;
post<T = any, R = Result<T>>(url: string, data?: any, config?: AxiosRequestConfig): Promise<R>;
put<T = any, R = Result<T>>(url: string, data?: any, config?: AxiosRequestConfig): Promise<R>;
patch<T = any, R = Result<T>>(url: string, data?: any, config?: AxiosRequestConfig): Promise<R>;
}
}
代码地址
- gitee:gitee.com/virtual1680…
- github:github.com/virtual1680…