vben 之 axios 封装
axios 封装,这是一个从新手入门就要开始做的一件事情,现在让我们看一下 vben 中是如何实现 axios 的封装的。
vben 中 axios 的封装
vben 中的 axios 封装的代码在packages\effects\request\src\request-client\request-client.ts
我们简单介绍一下这个封装,首先,vben 使用class
,然后类中有一些属性
ts
// 方法: 添加请求拦截器 添加响应拦截器
public addRequestInterceptor;
public addResponseInterceptor;
// 方法:下载 // 方法:上传
public download ;
public upload ;
// 是否正在刷新token
public isRefreshing = false;
// 刷新token队列
public refreshTokenQueue: ((token: string) => void)[] = [];
// axios实例
private readonly instance: AxiosInstance;
/** POST请求方法 */
public post<T = any>(url: string, data?: any, config?: RequestClientConfig): Promise<T> { }
/** 通用的请求方法 */
public async request<T>(url: string, config: RequestClientConfig): Promise<T> {}
addRequestInterceptor
和addResponseInterceptor
这两个特别简单,实际上就像下面代码一样,为了写起来简单和语义化,进行了一层封装。
ts
addRequestInterceptor(fulfilled,rejected){
this.instance.interceptors.request.use(fulfilled, rejected);
}
upload
和download
这个也很简单。
- 文件上传时,请求参数为 formData 的格式,而
upload
正是将json
格式的请求参数转为 formData 格式,同时设置请求头的Content-Type
为multipart/form-data
。 - 文件下载的封装也很简单,就是在请求头中,增加了一个
responseType
为blob
,这样,响应的数据,会自动转为 blob。如果要下载文件,则将 blob 转为 blob 的 url,并使用 a 标签进下载。 - 然而,这种做法仅适合小文件下载,因为 响应的数据 blob 会存在浏览器的内存中,此时,浏览器占用内存会变大,影响到浏览器性能。正确方法应该是后端返回文件在服务器的 url 地址,前端直接使用 a 标签进行下载。关于权限问题,可以使用 cookie 等其他技术方案解决。
isRefreshing
和refreshTokenQueue
这个会在后面的拦截器中用到,目前不做讨论。
axios
实例,这个也没啥好说的
post
和request
请求方法,这个也没啥好说的
问题讨论
1. 为什么进行封装?
目前,我们从上面学习得到的结论就是,通过封装,为调用者开发提供了便利,添加拦截器更加方便,自定义一些请求方法也方便,如下载和上传。
2. 为什么通过加一层进行封装?
减少对源码的污染,以及提高可读性和可维护性。
3. 为什么选择class
进行封装?
class
更加语义化,可读性和可维护性更高一些。
下集预告:预设拦截器
下面是 vben 中封装的 axios 代码
ts
class RequestClient {
public addRequestInterceptor: InterceptorManager["addRequestInterceptor"];
public addResponseInterceptor: InterceptorManager["addResponseInterceptor"];
public download: FileDownloader["download"];
// 是否正在刷新token
public isRefreshing = false;
// 刷新token队列
public refreshTokenQueue: ((token: string) => void)[] = [];
public upload: FileUploader["upload"];
private readonly instance: AxiosInstance;
/**
* 构造函数,用于创建Axios实例
* @param options - Axios请求配置,可选
*/
constructor(options: RequestClientOptions = {}) {
// 合并默认配置和传入的配置
const defaultConfig: RequestClientOptions = {
headers: {
"Content-Type": "application/json;charset=utf-8",
},
responseReturn: "raw",
// 默认超时时间
timeout: 10_000,
};
const { ...axiosConfig } = options;
const requestConfig = merge(axiosConfig, defaultConfig);
requestConfig.paramsSerializer = getParamsSerializer(requestConfig.paramsSerializer);
this.instance = axios.create(requestConfig);
bindMethods(this);
// 实例化拦截器管理器
const interceptorManager = new InterceptorManager(this.instance);
this.addRequestInterceptor = interceptorManager.addRequestInterceptor.bind(interceptorManager);
this.addResponseInterceptor = interceptorManager.addResponseInterceptor.bind(interceptorManager);
// 实例化文件上传器
const fileUploader = new FileUploader(this);
this.upload = fileUploader.upload.bind(fileUploader);
// 实例化文件下载器
const fileDownloader = new FileDownloader(this);
this.download = fileDownloader.download.bind(fileDownloader);
}
/**
* GET请求方法
*/
public get<T = any>(url: string, config?: RequestClientConfig): Promise<T> {
return this.request<T>(url, { ...config, method: "GET" });
}
/**
* POST请求方法
*/
public post<T = any>(url: string, data?: any, config?: RequestClientConfig): Promise<T> {
return this.request<T>(url, { ...config, data, method: "POST" });
}
/**
* 通用的请求方法
*/
public async request<T>(url: string, config: RequestClientConfig): Promise<T> {
try {
const response: AxiosResponse<T> = await this.instance({
url,
...config,
...(config.paramsSerializer ? { paramsSerializer: getParamsSerializer(config.paramsSerializer) } : {}),
});
return response as T;
} catch (error: any) {
throw error.response ? error.response.data : error;
}
}
}