2025年优雅的使用 TS 封装 Axios

2024-12-17 115 0

前言

axios 的库本身是一个基于ajax 封装通用的请求库。为满足个性化业务需求,通常需要进行再封装使用。

封装Axios 之前我们设定一个目标,我们理想中的 axios 是什么样的?

  • 请求拦截和统一的错误处理
  • 自定义接口返回类型
  • 封装前的资料学习

对于封装 Aios 无非我们是对请求前,响应体进行拦截,以及对返回的数据类型进行标注。以下是axios的流程图,由 ChatGPT 生成。在封装中也是对这几个流程进行自定义封装。

封装开始

这是在封装中主要用到的类型,其实也是对应着流程图中,几个主要流程。后面可以CTRL + 鼠标左键,点进去查看类型具体是如何定义的。

import axios, {
  /** axios实例类型 */
  AxiosInstance,
  /** 内部axios请求配置类型 */
  InternalAxiosRequestConfig,
  /** axios请求配置类型 */
  AxiosRequestConfig,
  /** 错误对象类型 */
  AxiosError,
  /** 完整原始响应体类型 */
  AxiosResponse,
} from "axios";

axios 最基础的封装

service 是类的成员,其实也就是axios 的实体,具体发送请求的对象。

在构造函数中对 axios 进行最基本的配置,设置 baseURL 以及请求超时时间

class HttpRequest {
  service: AxiosInstance;
  constructor() {
    /**
     * 创建axios实例
     * 设置一些默认配置项
     *
     * 项目API基础URL
     * 请求超时时间
     */
    this.service = axios.create({
      baseURL: import.meta.env.VITE_APP_API_URL,
      timeout: 5 * 1000,
    });
}

请求拦截器

请求拦截器中使用 this.service.interceptors.request.use 方法进去请求拦截,use方法分别接受 3 个 可选参数

点击 use 方法 可以查看到它的类型说明,它分别接收 onFulfilledonRejectedoptions 其实叫什么名字无所畏,管它叫张三、李四还是王二麻子,只是称呼为了方便就按照官方的叫法叫吧

第一个参数: onFulfilled

它的类型来源在源文件中都写的清楚明白,仔细看下可以看到它的类型是泛型V,下面泛型 V 就是 InternalAxiosRequestConfig,那么我们也就知道了 use 方法第一个参数的类型了可以直接定义上去。

作用:在常见的业务中,一般是将token 放在请求头上,作为鉴权使用,人类坐任何运动前的都会进行热身运动,才能更持久,对应电脑也是一样总要有准备运动。都可以放上去,总之在请求前你想做的一些事情你都可以放上去。

第二个参数: onRejected

onRejected 也是个方法,执行发生错误后执行,接收错误对象,一般我们请求没发送出去出现报错时,一般这边不会做什么操作

第三个参数: options

options 是个对象,配置 1. 设置拦截器是否同步执行 默认false 2. 设置拦截器是否运行

代码

import axios, {
  /** axios实例类型 */
  AxiosInstance,
  /** 内部axios请求配置类型 */
  InternalAxiosRequestConfig,
  /** axios请求配置类型 */
  AxiosRequestConfig,
  /** 错误对象类型 */
  AxiosError,
  /** 完整原始响应体类型 */
  AxiosResponse,
} from "axios";

class HttpRequest {
  service: AxiosInstance;
  constructor() {
// ...
/** 请求拦截器 */
    this.service.interceptors.request.use(
      (config: InternalAxiosRequestConfig) => {
        /**
         * set your config
         */
        if (import.meta.env.VITE_APP_TOKEN_KEY && getToken()) {
          // carry token
          config.headers[import.meta.env.VITE_APP_TOKEN_KEY] = getToken();
        }
        return config;
      },
      /**  执行发生错误后执行,接收错误对象,一般我们请求没发送出去出现报错时,执行的就是这一步
       * 请求错误拦截器
       *
       **/
      (error: AxiosError) => {
        return Promise.reject(error);
      },
      {
        /** 设置拦截器是否同步执行 默认false */
        synchronous: false,
        /** 设置拦截器是否运行 */
        runWhen: (config: InternalAxiosRequestConfig) => {
          return true;
        },
      }
    );
// ..
   }

响应拦截器

响应拦截和请求拦截器大体相同,这边主要讲响应拦截器的类型。响应拦截的类型在源代码中也可以看到和请求拦截器中一样,不同的是响应拦截器中的类型,官方提供一个泛型给我们自己定义,这样我们就可以定义后端返回的数据结构。

看到下面的图是不是和请求拦截器很像,只是我们做的事情略有不同 响应拦截器也接受三个可选参数 onFulfilled onRejected options

onFulfilled

接收一个方法,方法的第一个参数类型是 AxiosResponse<T> 这边的 T 也就是我们前端和后端约定的返回数据的格式由我们自定义 我这边使用 ResponseModel

在响应的时候我们只关注服务端给我们的响应即可,所以最后只需要返回 AxiosResponse["data"]

这边只需根据自己的业务需求来写就行。我给出的代码,只做一个参考。

代码

import axios, {
  /** axios实例类型 */
  AxiosInstance,
  /** 内部axios请求配置类型 */
  InternalAxiosRequestConfig,
  /** axios请求配置类型 */
  AxiosRequestConfig,
  /** 错误对象类型 */
  AxiosError,
  /** 完整原始响应体类型 */
  AxiosResponse,
} from "axios";


class HttpRequest {
  service: AxiosInstance;
  constructor() {
// ...
 /** 响应拦截器 */
    this.service.interceptors.response.use(
      (response: AxiosResponse<ResponseModel>): AxiosResponse["data"] => {
        /** 获取响应数据 */
        const { data } = response;
        /** 获取状态码 */
        const { code } = data;
        /**根据 code 做出错误处理 */
        if (code) {
          if (code != 200) {
            switch (code) {
              case 404:
                // the method to handle this code
                break;
              case 403:
                // the method to handle this code
                break;
              default:
                break;
            }
            return Promise.reject(data.message);
          } else {
            return data;
          }
        } else {
          return Promise.reject("Error! code missing!");
        }
      },
      (error: AxiosError) => {
        return Promise.reject(error);
      }
    );
// ..
   }

即将结束-封装请求方法

这一步其实,封装来说意义不大,因为在前面我们已经对axios几个关键流程进行了处理,这一步主要是为了我们后面方便调用。

import axios, {
  /** axios实例类型 */
  AxiosInstance,
  /** 内部axios请求配置类型 */
  InternalAxiosRequestConfig,
  /** axios请求配置类型 */
  AxiosRequestConfig,
  /** 错误对象类型 */
  AxiosError,
  /** 完整原始响应体类型 */
  AxiosResponse,
} from "axios";


class HttpRequest {
  service: AxiosInstance;
  constructor() {
// ...
 
  /**
   * 请求方法
   * 返回Promise<ResponseModel<T>>
   */
  request<T = any>(config: AxiosRequestConfig): Promise<ResponseModel<T>> {
    return new Promise((resolve, reject) => {
      try {
        this.service
          .request<ResponseModel<T>>(config)
          .then((response: AxiosResponse["data"]) => {
            resolve(response as ResponseModel<T>);
          })
          .catch((err) => {
            reject(err);
          });
      } catch (error) {
        reject(error);
      }
    });
  }

  /**封装CRUD */
  get<T = any>(config: AxiosRequestConfig): Promise<ResponseModel<T>> {
    return this.request<T>({ method: "GET", ...config });
  }

  post<T = any>(config: AxiosRequestConfig): Promise<ResponseModel<T>> {
    return this.request<T>({ method: "POST", ...config });
  }

  put<T = any>(config: AxiosRequestConfig): Promise<ResponseModel<T>> {
    return this.request<T>({ method: "PUT", ...config });
  }

  delete<T = any>(config: AxiosRequestConfig): Promise<ResponseModel<T>> {
    return this.request<T>({ method: "DELETE", ...config });
  }
// ..
   }

使用

这边我们可以自定义接口的返回的类型

httpRequest.get<string>({
    url: "/test",
    baseURL: "http://localhost:8081",
  }).then((r) => {
    console.log(r);
  });

相关文章

重学下 JS 类
快速搭建自己博客 wordPress 十分钟快速搭建
博客网站搭建得心路历程
十年老站的第一年 世界,您好!

发布评论