vben 之 axios 封装

417 阅读3分钟

vben 之 axios 封装

axios 封装,这是一个从新手入门就要开始做的一件事情,现在让我们看一下 vben 中是如何实现 axios 的封装的。

vben 中 axios 的封装

vben 中的 axios 封装的代码在packages\effects\request\src\request-client\request-client.ts

我们简单介绍一下这个封装,首先,vben 使用class,然后类中有一些属性

  // 方法: 添加请求拦截器 添加响应拦截器
  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> {}

addRequestInterceptoraddResponseInterceptor

这两个特别简单,实际上就像下面代码一样,为了写起来简单和语义化,进行了一层封装。

addRequestInterceptor(fulfilled,rejected){
  this.instance.interceptors.request.use(fulfilled, rejected);
}

uploaddownload

这个也很简单。

  1. 文件上传时,请求参数为 formData 的格式,而upload正是将json格式的请求参数转为 formData 格式,同时设置请求头的Content-Typemultipart/form-data
  2. 文件下载的封装也很简单,就是在请求头中,增加了一个responseTypeblob,这样,响应的数据,会自动转为 blob。如果要下载文件,则将 blob 转为 blob 的 url,并使用 a 标签进下载。
  3. 然而,这种做法仅适合小文件下载,因为 响应的数据 blob 会存在浏览器的内存中,此时,浏览器占用内存会变大,影响到浏览器性能。正确方法应该是后端返回文件在服务器的 url 地址,前端直接使用 a 标签进行下载。关于权限问题,可以使用 cookie 等其他技术方案解决。

isRefreshingrefreshTokenQueue

这个会在后面的拦截器中用到,目前不做讨论。

axios实例,这个也没啥好说的

postrequest请求方法,这个也没啥好说的

问题讨论

1. 为什么进行封装?

目前,我们从上面学习得到的结论就是,通过封装,为调用者开发提供了便利,添加拦截器更加方便,自定义一些请求方法也方便,如下载和上传。

2. 为什么通过加一层进行封装?

减少对源码的污染,以及提高可读性和可维护性。

3. 为什么选择class进行封装?

class更加语义化,可读性和可维护性更高一些。

下集预告:预设拦截器

下面是 vben 中封装的 axios 代码

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