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;
    }
  }
}