基于类封装axios,灵活控制,扩展自定义,已在生产环境上使用

317 阅读7分钟

在现代前端开发中,HTTP 请求的管理是一个至关重要的功能,尤其是在构建复杂的 Web 应用时。为了提高代码的复用性和维护性,开发者往往会对原生的请求库进行封装,以便提供更为清晰、灵活且可扩展的接口。

在开发中,封装 Axios 是为了提高代码的可复用性、可维护性以及提高对请求和响应的管理能力。通过TypeScript 封装 Axios,我们不仅能够简化 HTTP 请求的使用,还能利用 TypeScript 的类型系统,确保请求和响应数据的类型安全。下面,我们将通过逐步解释如何封装 Axios。

为什么封装 Axios

首先,原始的 Axios API 提供了直接的 HTTP 请求功能,但当项目逐渐增大时,直接使用 Axios 会面临以下问题:

  • 重复代码:每次请求都需要编写相同的代码(如处理请求头、错误处理等)。
  • 请求拦截和响应拦截:通常需要对请求或响应进行一些统一的处理(如添加 token、错误重试等)。
  • 类型安全:在请求和响应过程中,可能会出现类型不一致的情况,影响代码的安全性和可维护性。
  • 请求取消:有时需要在请求发送后,但在响应返回之前取消请求,尤其是在单页应用中,组件销毁时常常需要中止当前请求。

通过封装 Axios,所有这些问题都可以得到集中管理和统一处理。

本文将基于一段封装了 Axios 的 Core<T> 类,逐步分析每个代码块的功能,并解释这种封装方式的优点及应用场景。

好的,我们可以更深入地解析这段 Core<T> 类的实现,逐一分析它的设计思路、实现细节以及背后的最佳实践。

Core<T> 类的详细解析

1. 类的基本结构

export default class Core<T = any> {
  instance: AxiosInstance;
  isCancel: boolean;
  constructor(config?: RequestConfig<T, T>, callback?: Function) {
    this.instance = axios.create(config);
    this.isCancel = !!config?.isCancel;
    ...
  }
}
  • Core<T> 是一个通用的类,它接受一个泛型 T,代表请求和响应数据的类型。这使得该类在处理不同数据类型时,能够保持灵活性和类型安全。

  • 类的属性包括:

    • instance:一个 AxiosInstance,用来执行实际的 HTTP 请求。
    • isCancel:一个布尔值,表示是否启用了请求取消功能。

2. Axios 实例的创建

this.instance = axios.create(config);

axios.create(config) 是用来创建一个自定义配置的 Axios 实例。这个实例可以拥有自己独立的配置(如 baseURL、headers、timeout 等),并且所有通过 this.instance 发起的请求都会使用这些配置。

3. 请求取消的机制

请求取消的核心思想是通过拦截器和请求去重机制,避免重复发送相同请求,或者在请求发出后能够及时取消未完成的请求。

if (this.isCancel) {
  const requestInterceptor = (config: RequestConfig<T, T>) => {
    if (config.isCancel === false) {
      return config;
    }
    pending.remove(config); 
    pending.add(config);
    return config;
  };
  • this.isCanceltrue 时,说明启用了请求取消机制。然后,系统会注册两个拦截器:请求拦截器和响应拦截器,用来处理请求的去重与取消。
  • 请求拦截器首先检查 config.isCancel,如果为 false,则跳过取消操作;否则,会将当前请求加入 pending 列表,这个列表用于记录正在进行的请求。
  • pending.remove(config) 用来移除已经发出的请求,pending.add(config) 会将新请求添加到列表中,以便后续的取消操作。

4. 请求拦截器

const requestInterceptor = (config: RequestConfig<T, T>) => {
  if (config.isCancel === false) {
    return config;
  }
  pending.remove(config); 
  pending.add(config);
  return config;
};
  • 请求拦截器用于在请求发出前对请求配置做一些处理。常见的应用场景包括:修改请求头、添加认证 token、或者在请求重复时取消前一个请求。
  • 在这里,pending.remove(config) 是用来移除之前相同请求,pending.add(config) 则将当前请求添加到待处理的请求队列中。
  • 这个机制保证了如果一个请求未完成,且相同的请求已经被发出,那么新的请求会“覆盖”掉旧的请求,避免不必要的重复请求。

5. 响应拦截器

const responseInterceptor = (response: AxiosResponse<T>) => {
  pending.remove(response.config);
  return response;
};
  • 响应拦截器的作用是在收到响应时,对响应数据进行处理或者对请求进行清理。
  • 在这里,pending.remove(response.config) 的作用是将已经完成的请求从 pending 列表中移除,确保请求列表不会积压,避免内存泄漏。

6. 错误响应拦截器

const responseInterceptorCatch = (error: AxiosError) => {
  if (error.config) {
    pending.remove(error.config);
  }
  return Promise.reject(error);
};
  • 错误响应拦截器会捕获请求中的错误(如网络错误、响应超时等),并且移除相关的请求配置。
  • 同样,错误也会被传递给调用方,return Promise.reject(error) 会将错误信息返回,以便进一步处理。

7. 请求执行与返回值

core<R>(config: RequestConfig<T, R>): Promise<R> {
  return new Promise((resolve, reject) => {
    if (config.interceptors?.requestInterceptor) {
      config = config.interceptors.requestInterceptor(config);
    }
    if (config.isCancel && !this.isCancel) {
      pending.remove(config as any);
      pending.add(config as any);
    }
    this.instance
      .request<any, R>(config)
      .then((res) => {
        if (config.interceptors?.responseInterceptor) {
          res = config.interceptors.responseInterceptor(res);
        }
        resolve(res);
      })
      .catch((err: AxiosError) => {
        reject(err);
      })
      .finally(() => {
        if (config.isCancel && !this.isCancel) {
          pending.remove(config as any);
        }
      });
  });
}
  • 这里定义了一个 core 方法,负责真正执行 HTTP 请求。它通过 this.instance.request() 发起请求,并返回一个 Promise,该 Promise 会在请求完成后被解决或拒绝。

  • 请求配置中的 interceptors.requestInterceptorinterceptors.responseInterceptor 是可选的用户自定义拦截器。如果传入这些拦截器,core 会在请求发出前和响应接收到后调用它们。

  • finally 块中,使用 pending.remove(config) 确保无论请求成功还是失败,都将请求从 pending 请求列表中移除。

8. 封装常见 HTTP 请求方法

get<R>(url: string, data: object = {}, config: RequestConfig<R, R>): Promise<R> {
  return this.core({ ...config, url, params: data, method: 'GET' });
}

post<R>(url: string, data: object = {}, config: RequestConfig<R, R>): Promise<R> {
  return this.core({ ...config, url, data, method: 'POST' });
}

delete<R>(url: string, config: RequestConfig<R, R>): Promise<R> {
  return this.core({ ...config, url, method: 'DELETE' });
}
  • 这些方法封装了 HTTP 请求的不同类型(如 GET、POST、DELETE 等),每个方法都会调用 this.core(),并根据请求的类型调整请求配置。
  • url, data, 和 config 会一起传递给 core 方法,构成完整的请求配置。

9. 请求管理与清理

abort(config: RequestConfig<T, T>): void {
  pending.remove(config as any);
}

clear(): void {
  pending.clear();
}
  • abort 方法允许用户手动取消一个特定的请求,它通过从 pending 列表中移除请求来实现。
  • clear 方法则是清理所有的请求,通常用于页面卸载或者应用退出时,防止有未完成的请求仍然存在。

10. 总结:设计思想与优点

10.1 请求取消与去重

通过 pending 列表和请求拦截器,Core 类实现了请求的去重和取消功能。重复的请求会被排除,避免多次发出相同的请求。这是前端开发中对性能优化的一个重要实践,尤其是在处理复杂的异步操作时。

10.2 灵活的拦截器机制

请求和响应的拦截器提供了极大的灵活性,开发者可以在拦截器中根据需要修改请求或响应。例如,可以在请求拦截器中加入鉴权信息,在响应拦截器中统一处理错误或格式化数据。

10.3 可扩展性与可维护性

通过封装 Axios 的请求,Core<T> 类实现了 HTTP 请求的统一管理,使得网络请求的代码更具可复用性和易维护性。通过简单的接口调用,开发者可以方便地发起各种类型的请求,并且通过配置轻松控制请求行为(如超时、取消、重试等)。

10.4 错误处理与清理

通过在 catchfinally 块中处理错误和清理操作,Core<T> 类能够保证请求的完整性。即使请求出错,也能确保资源被及时释放,避免内存泄漏。

总的来说,这种封装提供了非常强大的功能,同时也提高了代码的可维护性和清晰度。对于复杂的前端应用,尤其是在处理大量异步请求时,这种封装能够显著简化请求管理和错误处理的复杂度。

这次是真对我以前项目真对axios 再次优化,目前没有仓库存在,因为最近计划开发一个灵活的微前端系统,把这个请求器放到这个项目里,作为一个基础工具库