ts 简易封装 axios,统一 API

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

为什么要封装

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

目标

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

文件结构

│  index.ts					# 实例化封装类实例
│
├─http
│      request.ts  			# 封装axios
│
└─modules
       login.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;  // hash
    filename?: 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();

        // 设置默认文件表单字段为 file
        const 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);
        }
        // 添加文件 hash
        if (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);
}

类型提示

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

ts 简易封装 axios,统一 API,Axios,axios,typescript,前端,封装,文件上传

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

ts 简易封装 axios,统一 API,Axios,axios,typescript,前端,封装,文件上传

  • 如果个别方法响应结构特殊,则可传入第三个泛型,自定义当前方法的响应结构
// 响应结构
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
    });
}

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

ts 简易封装 axios,统一 API,Axios,axios,typescript,前端,封装,文件上传

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

ts 简易封装 axios,统一 API,Axios,axios,typescript,前端,封装,文件上传文章来源地址https://www.toymoban.com/news/detail-742678.html

文件上传

/**
 * 文件上传
 */

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();

        // 设置默认文件表单字段为 file
        const 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);
        }
        // 添加文件 hash
        if (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;

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

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

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

相关文章

  • Vue3和TypeScript下基于Axios的二次封装教程

    学习如何在Vue3项目中使用TypeScript和Axios进行二次封装,实现更灵活的网络请求处理。详细教程包括拦截器设置和类型扩展。

    2024年02月05日
    浏览(136)
  • 四、axios在vite+ts使用class类二次封装

    引入需要的文件创建class类 index.ts 配置解决import.meta.env报错问题 status.ts types.ts api接口 创建element-puls.d.ts文件 完成后在script里用的弹框在ts不会报错

    2024年02月09日
    浏览(50)
  • SpringBoot&Vue&EmementUI前后端分离整合、统一封装axios、跨域配置

    🧑‍💻作者名称:DaenCode 🎤作者简介:CSDN实力新星,后端开发两年经验,曾担任甲方技术代表,业余独自创办智源恩创网络科技工作室。会点点Java相关技术栈、帆软报表、低代码平台快速开发。技术尚浅,闭关学习中······ 😎人生感悟:尝尽人生百味,方知世间冷暖。

    2024年02月09日
    浏览(41)
  • 前端框架前置学习(3) AJAX原理 XMLHttpRequest,Promise,简易axios函数封装

    1.获取图片文件对象  // 文件选择元素-change改变事件 document.querySelector(\\\'.upload\\\').addEventListener(\\\'change\\\', e = {       // 1. 获取图片文件       console.log(e.target.files[0]) 2.使用FormData携带文件  // 2. 使用 FormData 携带图片文件       const fd = new FormData()       fd.append(\\\'img\\\', e.target.files[0

    2024年02月03日
    浏览(64)
  • vue3+ts+vite中封装axios,使用方法从0到1

    http.ts文件内容: methods.ts文件内容: api/user.ts 用户相关接口 types/user.d.ts定义接口ts类型 请求成功测试

    2024年02月02日
    浏览(53)
  • 解决:用TS封装Axios报错TS2345:Argument of type ‘((config: AxiosRequestConfig<any>) => AxiosRequestConfig...

     代码没问题,但是一直报红线。    运行后报错,研究发现是axios的版本问题 解决方法: yarn add axios@next 加上next装的是:axios@1.2.0-alpha.1版本。 看来应该是新版本的bug 上官网查看版本信息: 1.2.3版本提到了这个问题。装上之后,果然可以用,但经过测试1.2.4后的版本都不行

    2024年02月16日
    浏览(44)
  • 鸿蒙API9+axios封装一个通用工具类

    使用方式: 打开Harmony第三方工具仓,找到axios,如图: 第三方工具仓网址:https://ohpm.openharmony.cn/#/cn/home 在你的项目执行命令:ohpm install @ohos/axios 前提是你已经装好了ohpm ,如果没有安装,可以在官网找到详细的安装教程; 注意:不是在你的entry目录下,比如你的项目名称:

    2024年04月17日
    浏览(60)
  • 【axios】-- axios 二次封装

    如baseUrl,超出时间等 出于权限和安全考虑的密钥设置到请求头 主要针对于错误的情况做全局统一处理 把对接口的请求封装为一个方法 例子

    2024年02月09日
    浏览(46)
  • axios 统一配置请求超时时间

    你可以通过配置 axios 的实例来统一设置请求的超时时间。以下是一个示例: 首先,安装 axios(如果还没有安装): 然后,在你的 Vue 项目中,你可以创建一个 axios 的实例,并设置默认的超时时间,然后将它应用到所有的请求。例如,你可以在项目的某个地方(例如 main.js )

    2024年02月03日
    浏览(38)
  • vue中axios的二次封装——vue 封装axios详细步骤

        api统一管理,不管接口有多少,所有的接口都可以非常清晰,容易维护。     通常我们的项目会越做越大,页面也会越来越多,如果页面非常的少,直接用axios也没有什么大的影响,那页面组件多了起来,上百个接口呢,这个时候后端改了接口,多加了一个参数什么的呢

    2024年02月02日
    浏览(54)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包