TypeScript封装Axios

这篇具有很好参考价值的文章主要介绍了TypeScript封装Axios。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

TypeScript封装Axios

Axios的基本使用

因axios基础使用十分简单,可参考axios官方文档,这里不在介绍他基本用法,主要讲解拦截器。
拦截器主要分为两种,请求拦截器响应拦截器
请求拦截器:请求发送之前进行拦截,应用于我们在请求发送前需要对请求数据做一些处理。例如:

  • 携带token
  • 当请求时间过长时,设置loading

响应拦截器:在响应到达时进行拦截,应用于在我们业务代码中拿到数据之前,需要对数据做一定处理。例如:

  • 转换数据格式
  • 移除loading

为什么要封装Axios

在项目中会有很多的模块都需要发送网络请求,常见的比如登录模块,首页模块等,如果我们项目中直接使用诸如axios.get(), axios.post(),会存在很多弊端,哪些弊端呢?

  1. 首先这样做会导致我们每个模块对axios依赖性太强,意味着我们项目中的每个模块都和一个第三方库耦合度较高,这样的话,如果axios不在维护,我们要更换库的时候将非常麻烦,我们可以假设一下,随着时间的推移,axios可能因为浏览器的升级,Webpack的改变而出现一些bug, 然而axios已不再维护,这时我们往往需要切换库,这就意味着我们需要去修改每个模块中的请求相关的代码,显而易见,非常繁琐。
  2. 还有一点,在我们发送网络请求的时候,往往会有很多共同的特性,比如说,在我们成功登录之后的其他请求中,我们往往需要在请求头中添加token,然后发送请求;在每次请求中,我们想展示一个loading… 这些功能如果在每次请求的逻辑中都写一遍,很明显,我们的代码重复度太高了。

而axios封装之后,则会带来很多好处:

解决以上弊端,降低与第三方库的耦合度,这样我们将来需要更换库时,只需要修改我们封装后的request即可,这样我们往往只是修改封装后一两个文件,而不再需要每个模块每个模块的修改。

在我们开发中,我认为class的相关语法封装性会更好,因此这里我选择尝试用类相关的概念来封装axios。我想要的封装后达到的效果:可以直接在其他项目使用。

利用面向对象的思想对Axios进行封装

基础封装

封装一个Request的类,使得在外部可以调用此类的构造函数创建实例,创建的实例就对应axios实例,http/request.ts中代码如下:

import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios";

// 创建这个类的目的:每个创建出的HDRequest的实例都对应一个axios实例
class Request {
  // 创建实例的方法:constructor()构造实例
  instance: AxiosInstance;
  constructor(config: AxiosRequestConfig) {
    this.instance = axios.create(config);
  }

  // 二次封装网络请求的方法
  request(config: AxiosRequestConfig) {
    return this.instance.request(config);
  }
}
// 暴露Request类
export default Request;

基本配置信息单独写在一个文件中,config/index.ts中代码如下:

const CONFIG = {
    // 服务器地址
    serverAddress: 'https://91huajian.cn',
    // 其他基础配置项,入最长响应时间等
};
export default CONFIG;

http/index.ts中创建一个Request类的一个实例http,并配置这个实例:

import Request from "./index";
import CONFIG from "@/config";

// 创建一个axios实例
const http = new Request({
  baseURL: CONFIG.serverAddress,
  timeout: CONFIG.maxTimeout,
})

export default http;

在接口中使用该实例发送请求:

// 在http/api/sponsor.ts文件中封装发送请求的方法,在页面组件任意位置随意调用
import http from '../request';
// 查询赞助
export const getSponsorListAsync: any = (params: any) => { 
    return http.request({
        url: '/huajian/common/getSponsorList',
        method: 'get',
        params: params
    });
}

拦截器的类型

拦截器分为三种:

  • 类拦截器(在封装的axios类(文中类为Request类)上定义的拦截器)
  • 实例拦截器(在利用Request类实例化对象时传递的参数中定义的拦截器)
  • 接口拦截器(在调用实例时传入的参数中定义的参数)

配置全局拦截器(类拦截)

保证每一个axios实例对象都有拦截器,即本使用Request实例化的对象发送的请求都会被配置的拦截器所拦截并执行拦截器中的程序。

类拦截器比较容易实现,只需要在类中对axios.create()创建的实例调用interceptors下的两个拦截器即可,实例代码如下:

import axios, { AxiosInstance,AxiosRequestConfig,AxiosResponse } from 'axios';
import { RequestConfig } from './types/types';
class Request {
  instance: AxiosInstance;
  constructor(config: RequestConfig) { 
    this.instance = axios.create(config);
    // 添加全局请求拦截器,每个实例都有
    this.instance.interceptors.request.use(
      // 拦截到请求中携带的所有配置项config
      (config: AxiosRequestConfig) => {
        console.log('全局请求拦截器', config);
        return config;
      },
      (err: any) => err
    )
    // 添加全局响应拦截器,每个实例都有
    this.instance.interceptors.response.use(
      // 拦截到服务器返回的响应体res
      (res: AxiosResponse) => {
        console.log('全局响应拦截器', res);
        return res.data;
      },
      (err: any) => err;
  }
  request(config: AxiosRequestConfig) { 
    return this.instance.request(config);
  }
}
export default Request;

我们在这里对响应拦截器做了一个简单的处理,就是将请求结果中的.data进行返回,因为我们对接口请求的数据主要是存在在.data中,跟data同级的属性我们基本是不需要的。

为某一Request实例单独配置拦截器(实例拦截)

实例拦截器是为了保证封装的灵活性,因为每一个实例中的拦截后处理的操作可能是不一样的,所以在定义实例时,允许我们传入拦截器。

新创建一个实例http2在它的config中传入拦截器属性,但是axiosAxiosRequestConfig类型中并没有拦截器属性类型。

因此需要对types/index.ts中的构造函数中的config类型进行扩展(extends)。首先我们定义一下interface,方便类型提示,代码如下:

import { AxiosRequestConfig, AxiosResponse } from "axios";
// 拦截器的类型
export interface RequestInterceptors<T> {
  // 请求拦截器
  requestInterceptor?: (config: AxiosRequestConfig) => AxiosRequestConfig;// 在发送请求之前做些什么
  requestInterceptorCatch?: (error: any) => any;// 对请求错误做些什么
  // 响应拦截器
  responseInterceptor?: (res: T) => T;// 对响应数据做点什么
  responseInterceptorsCatch?: (err: any) => any;// 对响应错误做点什么
}
// 自定义传入的参数
export interface RequestConfig<T =  AxiosResponse> extends AxiosRequestConfig{
  interceptors?: RequestInterceptors<T>;
}

然后再创建新的实例,并在实例中引入定义的拦截器:

import Request from "./index";
import CONFIG from "@/config";
import {RequestConfig} from "./types/types";
import { AxiosResponse } from "axios";

// 创建一个axios实例
const http = new Request({
  baseURL: CONFIG.serverAddress,
  timeout: CONFIG.maxTimeout
})
const http2 = new Request({
  baseURL: CONFIG.serverAddress,
  timeout: CONFIG.maxTimeout,
  interceptors: {
    // 配置请求拦截器
    requestInterceptor: (config: RequestConfig) => { 
      console.log('通过请求拦截器,拿到http2的请求配置参数',config);
      return config;
    },
    // 响应拦截器
    responseInterceptor: (result: AxiosResponse) => {
      console.log('通过响应拦截器,拿到http2的响应返回的结果',result);
      return result;
    }
  }
})

export default {http,http2};

注意:这里的拦截器只能由使用http2实例发送的请求才会执行。

我们的拦截器的执行顺序为实例请求→类请求→实例响应→类响应;这样我们就可以在实例拦截上做出一些不同的拦截,

此时在使用Request实例化对象http2时,我们传入的配置项中多了interceptors配置项,那么在Request类中我们就得接收并在实例化时执行:

import axios, { AxiosInstance,AxiosRequestConfig,AxiosResponse } from 'axios';
import { RequestConfig,RequestInterceptors } from './types/types';
class Request {
  instance: AxiosInstance;
  // 拦截器对象
  interceptorsObj?: RequestInterceptors<AxiosResponse>;
  constructor(config: RequestConfig) { 
    this.instance = axios.create(config);
    this.interceptorsObj = config.interceptors;//接收实例对象传入的该实例的定制拦截器
    
    // 全局请求拦截器
    this.instance.interceptors.request.use(
      (config: AxiosRequestConfig) => {
        console.log('全局请求成功拦截器', config);
        return config;
      },
      (err: any) => err
    )  
    // 使用实例对象的自定义拦截器 针对特定的http2实例添加拦截器
    this.instance.interceptors.request.use(
      this.interceptorsObj?.requestInterceptor, // 请求前的拦截器
      this.interceptorsObj?.requestInterceptorCatch // 发送请求失败的拦截器
    )
    // 使用实例对象的自定义拦截器 针对特定的http2实例添加拦截器
    this.instance.interceptors.response.use(
      config.interceptors?.responseInterceptor,
      config.interceptors?.responseInterceptorsCatch
    );
    // 全局响应拦截器
    this.instance.interceptors.response.use(
      (res: AxiosResponse) => {
        console.log('全局响应成功拦截器', res);
        return res.data;
      },
      (err: any) => {
        return err;
      }
    );
  }
  request(config: AxiosRequestConfig) { 
    return this.instance.request(config);
  }
}
export default Request;

同一个request实例的不同网络请求设置不同的拦截器(接口拦截)

现在我们对单一接口进行拦截操作,首先我们将AxiosRequestConfig类型修改为RequestConfig允许传递拦截器;然后我们在类拦截器中将接口请求的数据进行了返回,也就是说在request()方法中得到的类型就不是AxiosResponse类型了。

接口中同一个实例在发送不同的request请求时一个配置了拦截器一个没配拦截器

import { http2 } from "..";

http2.request({
    url: "/entire/list",
    params: {
      offset: 0,
      size: 20,
    },
  })
  .then((res) => {
    console.log(res);
  });

http2.request({
    url: "/home/highscore",
    interceptors: {
      responseInterceptor: (config) => {
        console.log("来自接口定制的请求前的拦截");
        return config;
      },
      responseInterceptor: (res) => {
        console.log("来自接口的响应成功的拦截");
        return res;
      },
    },
  })
  .then((res) => {
    console.log(res);
  });

request/index.tsrequest方法进行进一步封装,使之能够立即执行传进来的拦截器:

// Request类的request方法:
// 二次封装网络请求的方法
request<T>(config: RequestConfig<T>): Promise<T> { 
  return new Promise((resolve, reject) => {
    // 为同一个request实例的不同网络请求设置不同的拦截器
    // 不能将拦截器放在实例上,这样的话同一个实例的拦截器都是一样的了
    // 只能判断传进来的config中是否设置了拦截器,若设置了就直接执行
      
    // 执行this.instance.request(config)之前先执行requestInterceptor,并更新config
    if (config.interceptors?.requestInterceptor) {
      //立即调用拦截器函数执行
      config = config.interceptors.requestInterceptor(config);
    }
    // 由于执行完this.instance.request(config)之后才能对response结果进行拦截,是个异步的过程
    // 在Promise内部调用instance实例先执行this.instance.request(config),然后等待结果,之后以结果作为拦截器函数的参数进行调用
    this.instance
      .request<any, T>(config)
      .then((res) => {
        // 如果给单个响应设置拦截器,这里使用单个响应的拦截器
        if (config.interceptors?.responseInterceptor) {
          res = config.interceptors.responseInterceptor(res);
        }
        resolve(res);
      })
      .catch((err: any) => {
        reject(err);
      })
  })
}

各种请求拦截的执行顺序:

拦截器执行顺序:接口请求 -> 实例请求 -> 全局请求 -> 实例响应 -> 全局响应 -> 接口响应

实例请求和全局请求的先后顺序取决于在Requestconstructor()构造函数中两种请求的执行顺序。

取消请求

思路步骤:

  1. 创建一个数组用于存储控制器资源;
  2. 在请求拦截器中将控制器存入数组;
  3. 在响应拦截器中将控制器从数组中移除;
  4. 封装一个取消全部请求的方法;
  5. 封住一个可以取消指定请求的方法;

准备

我们需要将所有请求的取消方法保存到一个集合(这里我用的数组,也可以使用Map)中,然后根据具体需要去调用这个集合中的某个取消请求方法。

因此,我们首先进行类型定义:

// 一个取消请求对象,键位url,值为取消控制器
export interface CancelRequestSource { 
  // 取消请求的标识
  [index: string]: AbortController;
}

然后我们在Request类中定义储存取消请求对象的数组,和存放请求url的数组

/*
存放取消控制对象的集合
* 在创建请求后将取消控制对象 push 到该集合中
* 封装一个方法,可以取消请求,传入 url: string|string[]  
* 在请求之前判断同一URL是否存在,如果存在就取消请求
*/
cancelRequestSourceList ?: CancelRequestSource[];
/*
存放所有请求URL的集合
* 请求之前需要将url push到该集合中
* 请求完毕后将url从集合中删除
* 添加在发送请求之前完成,删除在响应之后删除
*/
requestUrlList ?: string[];

接着我们要准备两个方法,一个时根据url在取消控制对象数组中找到对应请求的方法,另一个时完成取消请求后删除存放url数组和存放取下请求对象数组中对象请求的方法。文章来源地址https://www.toymoban.com/news/detail-671164.html

//根据url找到取消请求对象数组中此次请求取消对象存放的地址
private getSourceIndex(url: string): number {
  return this.cancelRequestSourceList?.findIndex((item: CancelRequestSource) => {
    return Object.keys(item)[0] === url;
  }) as number;
}
//请求取消完成后,我们要删除对应请求和取消请求对象
private delUrl(url: string) {
  const urlIndex = this.requestUrlList?.findIndex((u) => u === url);
  const sourceIndex = this.getSourceIndex(url);
  // 删除url和AbortController对象
  urlIndex !== -1 && this.requestUrlList?.splice(urlIndex as number, 1);
  sourceIndex !== -1 && this.cancelRequestSourceList?.splice(sourceIndex as number, 1);
}

在发送请求前存入AbortController对象

const url = config.url;
// url存在 保存当前请求url 和 取消请求方法
if (url) {
  this.requestUrlList?.push(url);//将url存入url数组
  const controller = new AbortController();//构造实例化一个AbortController对象控制器
  config.signal = controller.signal//绑定请求
  this.cancelRequestSourceList?.push({
    [url]: controller//将该控制器添加入cancelRequestSourceList数组
  })
}

请求已经完成了删除保存的url和AbortController对象

this.instance
    .request<any, T>(config)
    .then(res => {
      // 如果我们为单个响应设置拦截器,这里使用单个响应的拦截器
      if (config.interceptors?.responseInterceptor) {
        res = config.interceptors.responseInterceptor<T>(res)
      }
      resolve(res)
    })
    .catch((err: any) => {
      reject(err)
    })
    .finally(() => {
      url && this.delUrl(url);// 请求执行完毕,删除保存在数组中的url和该请求的取消方法
    });

封装取消请求方法

  • 封装取消全部请求
// 取消全部请求
cancelAllRequest() {
    this.cancelRequestSourceList?.forEach((source) => {
      const key = Object.keys(source)[0];
      source[key].abort();
    })
}
  • 封装取消部分请求
// 取消请求
cancelRequest(url: string | string[]) {
  if (typeof url === 'string') {
    //  取消单个请求
    const sourceIndex = this.getSourceIndex(url);
    sourceIndex >= 0 && this.cancelRequestSourceList?.[sourceIndex][url].abort();
  } else {
    // 存在多个需要取消请求的地址
    url.forEach((u) => {
      const sourceIndex = this.getSourceIndex(u);
      sourceIndex >= 0 && this.cancelRequestSourceList?.[sourceIndex][u].abort();
    });
  }
}

到了这里,关于TypeScript封装Axios的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • TypeScript与JavaScript

    博主作品: 《Java项目案例》主要基于SpringBoot+MyBatis/MyBatis-plus+MySQL+Vue等前后端分离项目,可以在左边的分类专栏找到更多项目。《Uniapp项目案例》有几个有uniapp教程,企业实战开发。《微服务实战》专栏是本人的实战经验总结,《Spring家族及微服务系列》专注Spring、SpringMV

    2024年02月05日
    浏览(44)
  • 《前端面试题》- TypeScript - TypeScript的优/缺点

    简述TypeScript的优/缺点 优点 增强了代码的可读性和可维护性 包容性,js可以直接改成ts,ts编译报错也可以生成js文件,兼容第三方库,即使不是ts编写的 社区活跃,完全支持es6 缺点 增加学习成本 增加开发成本,因为增加了类型定义 需要编译,类型检查会增加编译时长,语

    2024年04月23日
    浏览(40)
  • 单例模式——javascript和typescript

    确保某个方法或者类只有一个是咧。而且自行实例子并向整个系统提供这个实例。 某个方法或类只能一个; 必须自行创建这个实例 必须自行向整个系统提供这个实例。

    2024年02月05日
    浏览(50)
  • 一文了解JavaScript 与 TypeScript的区别

    TypeScript 和 JavaScript 是两种互补的技术,共同推动前端和后端开发。在本文中,我们将带您快速了解JavaScript 与 TypeScript的区别。   一、TypeScript 和 JavaScript 之间的区别 JavaScript 和 TypeScript 看起来非常相似,但有一个重要的区别。 JavaScript 和 TypeScript 之间的主要区别在于 JavaS

    2024年02月14日
    浏览(51)
  • React + Typescript + Antd:封装通用的字典组件DXSelect

    在开发中,我们经常遇到这样的场景,在表单中,有个下拉框,选择对应的数据。 那么这个下拉框的选项,就是字典。一搬的做法是,通过antd的Select来实现,代码如下:

    2024年02月15日
    浏览(79)
  • TypeScript是什么?它与JavaScript有什么区别?

    面试题-TS(1):TypeScript是什么?它与JavaScript有什么区别? TypeScript是一种编程语言,它是JavaScript的超集。它通过添加静态类型、类、接口和模块等功能来扩展JavaScript。 JavaScript是一种广泛应用于Web开发的脚本语言,它的灵活性和易用性使得它成为了开发者们的首选。然而,JavaS

    2024年02月16日
    浏览(47)
  • React框架:TypeScript支持的JavaScript库

    React 框架是一个功能强大的 JavaScript 库,让用户可以轻松地构建高度动态的用户界面。它借助虚拟 DOM 的思想实现高效的性能,并具有易于使用和灵活的编程接口。随着越来越多的人开始使用 React ,在不断的发展和变化中, React 框架现在加入了 TypeScript 的支持,使其成为一个

    2024年02月11日
    浏览(65)
  • TypeScript:为什么JavaScript需要类型检查?

    JavaScript是当今最为流行的编程语言之一。它是一种高级的、解释性的编程语言,用于Web应用程序的开发。然而,JavaScript的灵活性也是它的弱点之一。JavaScript中的变量、函数、类等都是动态类型,这意味着它们的类型可以在运行时发生变化。虽然这种灵活性为JavaScript开发人员

    2024年02月04日
    浏览(56)
  • typeof 在TypeScript中和JavaScript中的区别

            在TypeScript中和JavaScript中都有typeOf,但是作用用法却大有不同。 一、typeof用来判断数据类型返回结果: 基本数据类型:string,number,boolean,undefined 引用数据类型:object (不管是什么引用类型就返回object),function 二、typeof判断变量是否存在         ts中的typeof可

    2024年02月09日
    浏览(49)
  • Vue框架:适用于TypeScript的JavaScript框架

    Vue 是一个高效、灵活、易于学习的 JavaScript 框架,它采用了 MVVM 架构,能够快速构建交互式的用户界面。作为一种现代化的框架,Vue已经成为了许多开发者的首选,其中也包括了很多使用 TypeScript 的开发者。 Vue 框架的最大特点是轻量级、易于上手、灵活和高效,这一点也是

    2024年02月11日
    浏览(41)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包