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,并更新configif (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数组和存放取下请求对象数组中对象请求的方法。

//根据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();});}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/52664.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

详解单体架构和微服务(概念,优缺点和区别)

单体架构和微服务 单体架构和微服务架构区别&#xff1f;为什么要用微服务架构&#xff1f; 单体架构的整个系统是一个War包&#xff0c;即war包走天下。微服务架构的项目是很多个war包&#xff08;一个子系统一个&#xff09;。 单体架构的优点: 架构简单开发测试部署简单…

Python读取Excel:实现数据高效处理的利器

目录 一、Python读取Excel的常用库二、Python读取Excel的步骤三、具体案例和使用场景四、Python读取Excel的优势与其他编程语言比较 摘要 本文将介绍Python读取Excel的方法&#xff0c;并通过具体案例和使用场景展示如何实现数据高效处理。我们将介绍常用的Python库&#xff0c…

[docker][WARNING]: Empty continuation line found in:

报警内容&#xff1a; 下面展示一些 内联代码片。 //执行 sudo docker build ubuntu:v1.00 . [WARNING]: Empty continuation line found in:出现上述错误原因为18行多了一个 " \" 符号&#xff0c;去除即可

macOS nginx部署前端项目

1、安装nginx&#xff1b; brew install nginx2、配置nginx&#xff0c;主要配置代码&#xff0c;服务器代理 1、配置文件地址 根目录是 macOS 文件系统的最顶层目录。您可以在 Finder 中使用快捷键 Shift Command G&#xff0c;然后输入 /usr&#xff0c;即可直接打开 /u…

c++ qt--QString,弹出框(第二部分)

c qt–QString&#xff0c;弹出框&#xff08;第二部分&#xff09; 一.QString 1.所用头文件 #include<QString>2.功能 1.初始化 可以用字符&#xff0c;常量字符串、字符指针、字符数组等类型给QString进行初始化 QString str2"4567";//进行初始化2.拼…

卷积神经网络实现天气图像分类 - P3

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f366; 参考文章&#xff1a;Pytorch实战 | 第P3周&#xff1a;彩色图片识别&#xff1a;天气识别&#x1f356; 原作者&#xff1a;K同学啊 | 接辅导、项目定制&#x1f680; 文章来源&#xff…

【LeetCode75】第三十五题 统计二叉树中好节点的数目

目录 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 代码&#xff1a; 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 给我们一棵二叉树&#xff0c;让我们统计这棵二叉树中好节点的数目。 那么什么是好节点&#xff0c;题目中给出定义&#xff0c;从根节点…

1782. 统计点对的数目

给你一个无向图&#xff0c;无向图由整数 n &#xff0c;表示图中节点的数目&#xff0c;和 edges 组成&#xff0c;其中 edges[i] [ui, vi] 表示 ui 和 vi 之间有一条无向边。同时给你一个代表查询的整数数组 queries 。 第 j 个查询的答案是满足如下条件的点对 (a, b) 的数…

MySQL图形化管理工具

MySQL图形化管理工具极大地方便了数据库的操作与管理&#xff0c;常用的图形化管理工具有&#xff1a;MySQL Workbench、phpMyAdmin、Navicat Preminum、MySQLDumper、SQLyog、dbeaver、MySQL ODBC Connector。 工具1&#xff1a;MySQL Workbench MySQL官方出品的工具。 工具…

实现高效消息传递:使用RabbitMQ构建可复用的企业级消息系统

文章目录 前言1.安装erlang 语言2.安装rabbitMQ3. 内网穿透3.1 安装cpolar内网穿透(支持一键自动安装脚本)3.2 创建HTTP隧道 4. 公网远程连接5.固定公网TCP地址5.1 保留一个固定的公网TCP端口地址5.2 配置固定公网TCP端口地址 前言 RabbitMQ是一个在 AMQP(高级消息队列协议)基…

【Linux】动态库和静态库

动态库和静态库 软链接硬链接硬链接要注意 自定义实现一个静态库(.a)解决、使用方法静态库的内部加载过程 自定义实现一个动态库&#xff08;.so&#xff09;动态库加载过程 静态库和动态库的特点 软链接 命令:ln -s 源文件名 目标文件名 软链接是独立连接文件的&#xff0c;他…

Servlet+JDBC实战开发书店项目讲解第15讲:项目开发总结

ServletJDBC实战开发书店项目讲解第15讲&#xff1a;项目开发总结 在本篇博客中&#xff0c;我们将对我们开发的书店管理系统进行详细总结。主要内容包括前后端的总结&#xff0c;从需求分析到编码实现&#xff0c;测试&#xff0c;最后上线的整个开发过程。 需求分析 在项目…

MongoDB +Dataframe+excel透视表

读取MongoDB中的表 from pymongo import MongoClient import pandas as pd client MongoClient(IP地址, 27017)db client[AOI] collection db[表名] #替换为实际的名称 document collection.find({time:{$gte:2023-08-15 15:26:06}})#筛选数据 df pd.DataFrame(list(docu…

Tomcat运行后localhost:8080访问自己编写的网页

主要是注意项目结构&#xff0c;home.html放在src/resources/templates下的home.html下&#xff0c;application.properties可以不做任何配置。还有就是关于web包的位置&#xff0c;作者一开始将web包与tabtab包平行&#xff0c;访问8080出现了此类报错&#xff1a; Whitelabel…

c++ qt--页面布局(第五部分)

c qt–页面布局&#xff08;第五部分&#xff09; 一.页面布局 在设计页面的左侧一栏的组件中我们可以看到进行页面布局的一些组件 布局组件的使用 1.水平布局 使用&#xff1a;将别的组件拖到水平布局的组件中即可&#xff0c;可以选择是在哪个位置 2.垂直布局 使用&…

【业务功能篇81】微服务SpringCloud-ElasticSearch-Kibanan-docke安装-入门实战

ElasticSearch 一、ElasticSearch概述 1.ElasticSearch介绍 ES 是一个开源的高扩展的分布式全文搜索引擎&#xff0c;是整个Elastic Stack技术栈的核心。它可以近乎实时的存储&#xff0c;检索数据&#xff1b;本身扩展性很好&#xff0c;可以扩展到上百台服务器&#xff0c;…

GB28181设备接入侧如何对接外部编码后音视频数据并实现预览播放

技术背景 我们在对接GB28181设备接入模块的时候&#xff0c;遇到这样的技术诉求&#xff0c;好多开发者期望能提供编码后&#xff08;H.264/H.265、AAC/PCMA&#xff09;数据对接&#xff0c;确保外部采集设备&#xff0c;比如无人机类似回调过来的数据&#xff0c;直接通过模…

金字塔原理(思考的逻辑)

前言&#xff1a;前面学习了表达的逻辑&#xff0c;那在表达之前&#xff0c;如何组织内容&#xff1f;如何进行思考&#xff1f;接下来看第二篇——思考的逻辑。 目录 应用逻辑顺序 时间顺序 结构顺序 程度顺序 概括各组思想 什么是概括&#xff1f; 思想表达方式 如…

C语言学习笔记---指针进阶01

C语言程序设计笔记---016 C语言指针进阶前篇1、字符指针2、指针数组2.1、指针数组例程1 -- 模拟一个二维数组2.2、指针数组例程2 3、数组指针3.1、回顾数组名&#xff1f;3.2、数组指针定义与初始化&#xff08;格式&#xff09;3.3、数组指针的作用 --- 常用于二维数组3.4、数…

Vue中使用element-plus中的el-dialog定义弹窗-内部样式修改-v-model实现-demo

效果图 实现代码 <template><el-dialog class"no-code-dialog" v-model"isShow" title"没有收到验证码&#xff1f;"><div class"nocode-body"><div class"tips">请尝试一下操作</div><d…