ts 简易封装 axios,统一 API

文章目录

    • 为什么要封装
    • 目标
    • 文件结构
    • 封装通用请求方法
    • 获得类型提示
    • http 方法
    • 文件上传
    • 使用示例
      • 实例化
      • post 请求
      • 类型提示
      • 文件上传
    • 总结
    • 完整代码:

为什么要封装

axios 本身已经很好用了,看似多次一举的封装则是为了让 axios 与项目解耦。
比如想要将网络请求换成 fetch,那么只需按之前暴露的 api 重新封装一下 fetch 即可,并不需要改动项目代码。

目标

  1. 统一请求API
  2. 使用接口数据时能有代码提示

文件结构

│  index.ts					# 实例化封装类实例
│
├─http
│      request.ts  			# 封装axios
│
└─moduleslogin.ts				# 业务模块upload.ts

封装通用请求方法

先封装一个通用的方法 request,然后在此基础上封装出 http 方法:

class HttpRequest {private readonly instance: AxiosInstance;constructor(config: AxiosRequestConfig) {this.instance = axios.create(config);}request<TReqBodyData, TResData, TResStructure = ResStructure<TResData>>(config: AxiosRequestConfig<TReqBodyData>): Promise<TResStructure> {return new Promise<TResStructure>((resolve, reject) => {this.instance.request<any, AxiosResponse<TResStructure>>(config).then(res => {// 返回接口数据resolve(res?.data);}).catch(err => reject(err));});}
}

获得类型提示

我希望在使用请求方法时,可以得到后端接口请求参数的提示,并且希望在使用响应结果时,也能得到类型提示。

因此设计了三个泛型:

  1. TReqBodyData:请求体类型
  2. TResStructure:接口响应结构类型
    1. TResData:接口响应 data 字段数据类型

并提供了一个默认的响应结构。使用时可以根据需要改成项目中通用的接口规则。当然在具体方法上也支持自定义响应接口结构,以适应一些不符合通用接口规则的接口。

/** 默认接口返回结构 */
export interface ResStructure<TResData = any> {code: number;data: TResData;msg?: string;
}

http 方法

由 request 方法封装出 http 方法同名的 api。

get<TReqBodyData, TResData, TResStructure = ResStructure<TResData>>(config?: AxiosRequestConfig<TReqBodyData>
): Promise<TResStructure> {return this.request({ ...config, method: "GET" });
}post<TReqBodyData, TResData, TResStructure = ResStructure<TResData>>(config: AxiosRequestConfig<TReqBodyData>
): Promise<TResStructure> {return this.request({ ...config, method: "POST" });
}
...

文件上传

文件上传一般使用 formdata,我们也可以简易封装一下。

uploadFile 方法接收 4 个参数:

  1. axios config 对象
  2. 表单内容
    1. 文件对象
    2. 文件对象的表单字段名
    3. hash
    4. 文件名
    5. 更多的表单数据(可通过泛型 TOtherFormData 指定类型)
  3. 上传进度回调
  4. 取消上传的 signal
export interface UploadFileParams<TOtherFormData = Record<string, any>>  {file: File | Blob;  // 文件对象fileHash?: string;  // hashfilename?: string;  // 文件名filed?: string;     // formdata 中文件对象的字段formData?: TOtherFormData; // 文件其他的参数(对象 key-value 将作为表单数据)
}/*** 文件上传* @param {AxiosRequestConfig} config axios 请求配置对象* @param {UploadFileParams} params 待上传文件及其一些参数* @param {(event: AxiosProgressEvent) => void} uploadProgress 上传进度的回调函数* @param {AbortSignal}cancelSignal 取消axios请求的 signal* @returns*/uploadFile<TOtherFormData>(config: AxiosRequestConfig,params: UploadFileParams<TOtherFormData>,uploadProgress?: (event: AxiosProgressEvent) => void,cancelSignal?: AbortSignal) {const formData = new window.FormData();// 设置默认文件表单字段为 fileconst customFilename = params.filed ?? "file";// 是否指定文件名,没有就用文件本来的名字if (params.filename) {formData.append(customFilename, params.file, params.filename);formData.append("filename", params.filename);} else {formData.append(customFilename, params.file);}// 添加文件 hashif (params.fileHash) {formData.append("fileHash", params.fileHash);}// 是否有文件的额外信息补充进表单if (params.formData) {Object.keys(params.formData).forEach(key => {const value = params.formData![key as keyof TOtherFormData];if (Array.isArray(value)) {value.forEach(item => {formData.append(`${key}[]`, item);});return;}formData.append(key, value as any);});}return this.instance.request({...config,method: "POST",timeout: 60 * 60 * 1000, // 60分钟data: formData,onUploadProgress: uploadProgress,signal: cancelSignal,headers: {"Content-type": "multipart/form-data;charset=UTF-8"}});}

使用示例

实例化

import HttpRequest from "./request";/** 实例化 */
const httpRequest = new HttpRequest({baseURL: "http://localhost:8080",timeout: 10000
});

post 请求

/** post 请求 */// 定义请求体类型
interface ReqBodyData {user: string;age: number;
}// 定义接口响应中 data 字段的类型
interface ResDataPost {token: string;
}export function postReq(data: ReqBodyData) {return httpRequest.post<ReqBodyData, ResDataPost>({url: "/__api/mock/post_test",data: data});
}
/** 发起请求 */
async function handleClickPost() {const res = await postReq({ user: "ikun", age: 18 });console.log(res);
}

类型提示

获得使用请求方法时的请求接口参数类型提示:

获得请求接口时的参数类型提示

获得接口默认响应结构的提示:

获得接口默认响应结构的提示

  • 如果个别方法响应结构特殊,则可传入第三个泛型,自定义当前方法的响应结构
// 响应结构
interface ResStructure {code: number;list: string[];type: string;time: number;
}
function postReq(data: ReqBodyData) {return httpRequest.post<ReqBodyData, any, ResStructure>({url: "/__api/mock/post_test",data: data});
}

当前方法自定义接口响应结构:

自定义响应结构

获得接口响应中 data 字段的提示:

获得接口响应中 data 字段的提示

文件上传

/*** 文件上传*/interface OtherFormData {fileSize: number;
}function uploadFileReq(fileInfo: UploadFileParams<OtherFormData>,onUploadProgress?: (event: AxiosProgressEvent) => void,signal?: AbortSignal
) {return httpRequest.uploadFile<OtherFormData>({baseURL: import.meta.env.VITE_APP_UPLOAD_BASE_URL,url: "/upload"},fileInfo,onUploadProgress,signal);
}
// 发起请求const controller = new AbortController();async function handleClickUploadFile() {const file = new File(["hello"], "hello.txt", { type: "text/plain" });const res = await uploadFileReq({ file, fileHash: "xxxx", filename: "hello.txt", formData: { fileSize: 1024 } },event => console.log(event.loaded),controller.signal);console.log(res);
}

总结

  1. 在通用请求方法 request 基础上封装了同名的 http 方法
  2. 使用泛型可获得请求参数和请求结果的类型提示
  3. 额外封装了文件上传的方法

完整代码:

import axios, { AxiosInstance, AxiosProgressEvent, AxiosRequestConfig, AxiosResponse } from "axios";export interface UploadFileParams<TOtherFormData = Record<string, any>> {file: File | Blob;fileHash?: string;filename?: string;filed?: string;formData?: TOtherFormData; // 文件其他的参数(对象 key-value 将作为表单数据)
}/** 默认接口返回结构 */
export interface ResStructure<TResData = any> {code: number;data: TResData;msg?: string;
}/*** A wrapper class for making HTTP requests using Axios.* @class HttpRequest* @example* // Usage example:* const httpRequest = new HttpRequest({baseURL: 'http://localhost:8888'});* httpRequest.get<TReqBodyData, TResData, TResStructure>({ url: '/users/1' })*   .then(response => {*     console.log(response.name); // logs the name of the user*   })*   .catch(error => {*     console.error(error);*   });** @property {AxiosInstance} instance - The Axios instance used for making requests.*/
class HttpRequest {private readonly instance: AxiosInstance;constructor(config: AxiosRequestConfig) {this.instance = axios.create(config);}/*** Sends a request and returns a Promise that resolves with the response data.* @template TReqBodyData - The type of the request body.* @template TResData - The type of the `data` field in the `{code, data}` response structure.* @template TResStructure - The type of the response structure. The default is `{code, data, msg}`.* @param {AxiosRequestConfig} [config] - The custom configuration for the request.* @returns {Promise<TResStructure>} - A Promise that resolves with the response data.* @throws {Error} - If the request fails.** @example* // Sends a GET request and expects a response with a JSON object.* const response = await request<any, {name: string}>({*   method: 'GET',*   url: '/users/1',* });* console.log(response.name); // logs the name of the user*/request<TReqBodyData, TResData, TResStructure = ResStructure<TResData>>(config: AxiosRequestConfig<TReqBodyData>): Promise<TResStructure> {return new Promise<TResStructure>((resolve, reject) => {this.instance.request<any, AxiosResponse<TResStructure>>(config).then(res => {// 返回接口数据resolve(res?.data);}).catch(err => reject(err));});}/*** 发送 GET 请求* @template TReqBodyData 请求体数据类型* @template TResData 接口响应 data 字段数据类型* @template TResStructure 接口响应结构,默认为 {code, data, msg}* @param {AxiosRequestConfig} config 请求配置* @returns {Promise} 接口响应结果*/get<TReqBodyData, TResData, TResStructure = ResStructure<TResData>>(config?: AxiosRequestConfig<TReqBodyData>): Promise<TResStructure> {return this.request({ ...config, method: "GET" });}/*** 发送 post 请求* @template TReqBodyData 请求体数据类型* @template TResData 接口响应 data 字段数据类型* @template TResStructure 接口响应结构,默认为 {code, data, msg}* @param {AxiosRequestConfig} config 请求配置* @returns {Promise} 接口响应结果*/post<TReqBodyData, TResData, TResStructure = ResStructure<TResData>>(config: AxiosRequestConfig<TReqBodyData>): Promise<TResStructure> {return this.request({ ...config, method: "POST" });}patch<TReqBodyData, TResData, TResStructure = ResStructure<TResData>>(config: AxiosRequestConfig<TReqBodyData>): Promise<TResStructure> {return this.request({ ...config, method: "PATCH" });}delete<TReqBodyData, TResData, TResStructure = ResStructure<TResData>>(config?: AxiosRequestConfig<TReqBodyData>): Promise<TResStructure> {return this.request({ ...config, method: "DELETE" });}/*** 获取当前 axios 实例*/getInstance(): AxiosInstance {return this.instance;}/*** 文件上传* @param {AxiosRequestConfig} config axios 请求配置对象* @param {UploadFileParams} params 待上传文件及其一些参数* @param {(event: AxiosProgressEvent) => void} uploadProgress 上传进度的回调函数* @param {AbortSignal}cancelSignal 取消axios请求的 signal* @returns*/uploadFile<TOtherFormData = any>(config: AxiosRequestConfig,params: UploadFileParams<TOtherFormData>,uploadProgress?: (event: AxiosProgressEvent) => void,cancelSignal?: AbortSignal) {const formData = new window.FormData();// 设置默认文件表单字段为 fileconst customFilename = params.filed || "file";// 是否指定文件名,没有就用文件本来的名字if (params.filename) {formData.append(customFilename, params.file, params.filename);formData.append("filename", params.filename);} else {formData.append(customFilename, params.file);}// 添加文件 hashif (params.fileHash) {formData.append("fileHash", params.fileHash);}// 是否有文件的额外信息补充进表单if (params.formData) {Object.keys(params.formData).forEach(key => {const value = params.formData![key as keyof TOtherFormData];if (Array.isArray(value)) {value.forEach(item => {// 对象属性值为数组时,表单字段加一个[]formData.append(`${key}[]`, item);});return;}formData.append(key, value as any);});}return this.instance.request({...config,method: "POST",timeout: 60 * 60 * 1000, // 60分钟data: formData,onUploadProgress: uploadProgress,signal: cancelSignal,headers: {"Content-type": "multipart/form-data;charset=UTF-8"}});}
}export default HttpRequest;

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

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

相关文章

【广州华锐互动】风景区规划VR虚拟现实教学系统

风景区规划VR虚拟现实教学系统是一种新兴的教学工具&#xff0c;它可以通过虚拟现实技术&#xff0c;为学生提供一种沉浸式的、实时的、全方位的景区规划体验。 在风景区规划VR虚拟现实教学系统中&#xff0c;学生可以通过虚拟现实技术&#xff0c;实时地与景区进行交互。他们可…

Spring - 手写模拟Spring底层原理

手写Spring 定义配置类AppConfig ComponentScan("com.spring.zsj") public class AppConfig {Beanpublic ApplicationListener applicationListener() {return new ApplicationListener() {Overridepublic void onApplicationEvent(ApplicationEvent event) {System…

【WinForm详细教程五】WinForm中的MenuStrip 、ContextMenuStrip 、ToolStrip、StatusStrip控件

文章目录 1.MenuStrip2.ContextMenuStrip3.ToolStrip4.StatusStrip 1.MenuStrip MenuStrip作为一个容器可以包含多个菜单项。MenuStrip 的重要属性包括&#xff1a; Name&#xff1a;菜单的名字Dock&#xff1a;菜单的停靠位置Items&#xff1a;菜单项的集合 ToolStripMenuI…

前端接口请求支持内容缓存和过期时间

前端接口请求支持内容缓存和过期时间 支持用户自定义缓存时间&#xff0c;在规则时间内读取缓存内容&#xff0c;超出时间后重新请求接口 首先封装一下 axios&#xff0c;这一步可做可不做。但是在实际开发场景中都会对 axios 做二次封装&#xff0c;我们在二次封装的 axios …

vue3中解析地址(address-parse插件的使用)

1.安装 npm install address-parse --save 2.使用 // 引入address-parse import AddressParse, { AREA, Utils } from "address-parse";const adressValue ref([])const getResolutionContent () > {const [result] AddressParse.parse(EnterpriseSalesDetai…

JAVA虚拟机-第2章 Java自动内存管理-异常实践

Java堆溢出 堆的参数设置&#xff1a;将堆的最小值-Xms参数与最大值-Xmx参数设置 public class HeapOOM {static class OOMObject {}public static void main(String[] args) {List<OOMObject> list new ArrayList<OOMObject>();while (true) {list.add(new OO…

JTS: 13 Polygonizer 多线合成面

这里写目录标题 版本代码 版本 org.locationtech.jts:jts-core:1.19.0 链接: github 代码 线段 生成之后的面 public class GeometryPolygonization {private static final GeometryFactory geometryFactory new GeometryFactory();private static final Logger LOGGER …

陪诊系统|挂号陪护搭建二开陪诊师入驻就医小程序

我们的陪诊小程序拥有丰富多样的功能&#xff0c;旨在最大程度满足现代人的需求。首先&#xff0c;我们采用了智能排队系统&#xff0c;通过扫描二维码获取排号信息&#xff0c;让您从繁琐的排队过程中解放出来。其次&#xff0c;我们提供了多种支付方式&#xff0c;不仅可以实…

管理类联考——数学——汇总篇——知识点突破——代数——函数、方程——记忆

文章目录 考点记忆/考点汇总——按大纲 整体局部 本篇思路&#xff1a;根据各方的资料&#xff0c;比如名师的资料&#xff0c;按大纲或者其他方式&#xff0c;收集/汇总考点&#xff0c;即需记忆点&#xff0c;在通过整体的记忆法&#xff0c;比如整体信息很多&#xff0c;通常…

python线程(进程子单位)

进程是由CPU给分配的执行单元&#xff0c;比较消耗空间和内存 创建、使用线程 import threading# 进程 # 线程 from time import sleepdef download():list1 ["girl.png", "boy.png", "child.png"]for l in list1:print(l)sleep(1.5)print(&qu…

变量的作用域

在javascript中&#xff0c;var定义的变量实际是有作用域的。 1 假设在函数体内声明&#xff0c;但在函数体外不可以使用 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Title</title> </…

Java实验一编程环境使用

1&#xff0e;String类的常用方法&#xff08;StringExample.java&#xff09; package step1;public class StringExample {public static void main(String args[]) {String s1 new String("you are a student");String s2 new String("how are you")…

Lodash 真的死了吗?Lodash 5 在哪里?

与一些传言相反&#xff0c;Lodash依然活跃&#xff0c;并正在迈向Lodash 5的发布&#xff01; Lodash 是那些为 JavaScript 提供便利功能的实用程序库之一&#xff0c;它使编程变得更加轻松。许多开发者使用它来简化对象和数组的处理。 它也是一个像 Moment.js那样被捕获得措手…

038-第三代软件开发-简易视频播放器-自定义Slider (二)

第三代软件开发-简易视频播放器-自定义Slider (二) 文章目录 第三代软件开发-简易视频播放器-自定义Slider (二)项目介绍简易视频播放器自定义Slider (二)横向纵向 关键字&#xff1a; Qt、 Qml、 关键字3、 关键字4、 关键字5 项目介绍 欢迎来到我们的 QML & C 项目&…

【UE】从UI中拖拽生成物体

目录 效果 步骤 一、准备工作 二、创建UI 三、创建Actor 四、拖拽生成Actor的逻辑 效果 步骤 一、准备工作 1. 首先新建一个第三人称模板工程 2. 新建一个游戏模式基础&#xff0c;这里命名为“BP_UIGameMode” 在世界场景设置中设置游戏模式覆盖为“BP_UIGameMode”…

Vue中切换tab路由,提示this.$confirm确定和取消执行不同的逻辑

beforeRouteLeave (to, from, next) { // 离开页面 if (this.editFlag true) { this.$confirm(页面尚未保存&#xff0c;确认离开吗?, 提示, { distinguishCancelAndClose: true, // 区分取消和关闭 confirmButtonText: 确定, cancelButtonText: 取消, type: info }).then(()…

RT-Thread 9. VS2012下仿真RT-Thread 和LVGL

1. 在ENV中添加组件 2. 下载组件 3. 生成代码 4. 打开代码 双击project.vcxproj 编译 5. 运行

Linux | 如何保持 SSH 会话处于活动状态

在远程服务器管理和安全数据传输中&#xff0c;SSH&#xff08;Secure Shell&#xff09;是不可或缺的工具。然而&#xff0c;它的便利性和安全性有时会因常见的问题而受到损害&#xff1a;冻结 SSH 会话。 此外&#xff0c;session 的突然中断可能会导致工作丢失、项目延迟和无…

深度学习_1 介绍;安装环境

深度学习 学习自李沐老师的课程。笔记主要以总结老师所讲解的内容以及我个人的想法为主&#xff0c;侵删&#xff01; 课程链接&#xff1a;课程安排 - 动手学深度学习课程 (d2l.ai) 介绍 AI地图&#xff1a; 我们以前写的非 AI 类程序基本都是人自己去想会遇到什么样的问题…

基于transformer的解码decode目标检测框架(修改DETR源码)

提示:transformer结构的目标检测解码器,包含loss计算,附有源码 文章目录 前言一、main函数代码解读1、整体结构认识2、main函数代码解读3、源码链接二、decode模块代码解读1、decoded的TransformerDec模块代码解读2、decoded的TransformerDecoder模块代码解读3、decoded的De…