axios+ts封装实践笔记

在现代web开发中,与后端服务进行稳定且高效的通信是至关重要的。为了达到这一目标,Axios作为一个基于Promise的HTTP客户端,因其简洁的API和强大的功能而广受欢迎。而TypeScript,作为JavaScript的超集,提供了静态类型检查,让开发者能够在编写代码的时候就能发现潜在的错误,从而提高了代码的健壮性和可维护性。本文将探讨如何将Axios和TypeScript结合使用,并提供一个封装实践的例子,以展示如何创建一个类型安全且易于使用的HTTP服务。

一.目录结构

二.请求类封装

typescript 复制代码
import axios from 'axios';
import type { AxiosError, AxiosInstance, AxiosResponse, InternalAxiosRequestConfig } from 'axios';
import type { MyInternalRequestConfig, MyRequestConfig } from './type'
import { getToken } from '../../utils/auth';



// 拦截器 蒙版Loading/token/修改配置
class MyRequest {
    instance: AxiosInstance;
    // request示例 => axios实例
    constructor(config: MyRequestConfig) {
        this.instance = axios.create(config)

        // 每个instance都拦截器
        this.instance.interceptors.request.use(
            (config: InternalAxiosRequestConfig) => {
                // loading/token
                const token = getToken()
                if (token) {
                    config.headers.Authorization = `Bearer ${token}`
                }
                console.log("全局请求成功拦截")
                return config;
            }, (error: AxiosError) => {
                console.log("全局请求失败的拦截", error)
                return Promise.reject(error)
            })

        // 响应拦截
        this.instance.interceptors.response.use(
            (res: AxiosResponse) => {
                console.log("全局响应成功的拦截")
                const { data } = res
                // 处理res.code
                return data;
            }, (error: AxiosError) => {
                console.log("全局响应失败的拦截", error)
                const { message } = error
                // 根据message 和status处理错误
                this.handleErrorMessage(message)
                return Promise.reject(error)
            })
        // 针对具有自定义拦截器的config
        this.instance.interceptors.request.use(
            config.interceptors?.requestSuccessInterceptor,
            config.interceptors?.requestFailInterceptor
        )
        this.instance.interceptors.response.use(
            config.interceptors?.responseSuccessInterceptor,
            config.interceptors?.responseFailInterceptor
        )
    }

    // 封装错误消息处理
    handleErrorMessage(message: string) {

    }

    // 封装网络请求方法
    request<T = any>(config: MyRequestConfig<T>) {
        // 单次拦截器
        if (config.interceptors?.requestSuccessInterceptor) {
            console.log("url请求成功拦截", config.url)
            config = config.interceptors.requestSuccessInterceptor(config as MyInternalRequestConfig)
        }
        return new Promise<T>((resolve, reject) => {
            try {
                // 在尝试发送请求之前,捕获可能的配置错误
                this.instance.request<any, T>(config).then(res => {
                    if (config.interceptors?.responseSuccessInterceptor) {
                        console.log("url响应成功拦截", config.url)
                        res = config.interceptors.responseSuccessInterceptor(res)
                    }
                    resolve(res)
                }).catch(error => {
                    if (config.interceptors?.responseFailInterceptor) {
                        console.log("url响应失败拦截", config.url)
                        error = config.interceptors.responseFailInterceptor(error)
                    }
                    reject(error)
                })
            } catch (e: any) {
                if (config.interceptors?.requestFailInterceptor) {
                    console.log("url请求失败拦截", config.url)
                    const modifiedError = config.interceptors.requestFailInterceptor(e)
                    return Promise.reject(modifiedError)
                } else {
                    return Promise.reject(e)
                }
            }

        })
    }

    // 封装常用方法
    get<T = any>(config: MyRequestConfig<T>) {
        return this.request({ ...config, method: "GET" })
    }
    post<T = any>(config: MyRequestConfig<T>) {
        return this.request({ ...config, method: "POST" })
    }
    put<T = any>(config: MyRequestConfig<T>) {
        return this.request({ ...config, method: "PUT" })
    }
    delete<T = any>(config: MyRequestConfig<T>) {
        return this.request({ ...config, method: "DELETE" })
    }
}


export default MyRequest;

MyRequest类通过构造函数初始化一个Axios实例,并对其进行配置和拦截器的设置。构造函数接收一个MyRequestConfig类型的参数,这个参数类型可能是对Axios请求配置的扩展。

js 复制代码
this.instance = axios.create(config)

上面的代码创建了一个新的Axios实例,并将传入的配置应用到这个实例上。 在构造函数中设置了全局的请求和响应拦截器。
请求拦截器 :在请求发送前,会检查是否有可用的token,并将其以Bearer Token的形式添加到请求头中。同时在控制台打印出"全局请求成功拦截"的信息。如果请求失败,会在控制台打印出失败信息。
响应拦截器 :在响应接收后,会在控制台打印出"全局响应成功的拦截"的信息,并返回响应的数据。如果响应失败,会调用handleErrorMessage方法处理错误信息,并返回rejected的Promise。

自定义拦截器设置

此外,还可以为特定的实例设置自定义的拦截器。这些自定义拦截器是通过构造函数的配置参数传入的。

typescript 复制代码
this.instance.interceptors.request.use(
  config.interceptors?.requestSuccessInterceptor,
  config.interceptors?.requestFailInterceptor
)
this.instance.interceptors.response.use(
  config.interceptors?.responseSuccessInterceptor,
  config.interceptors?.responseFailInterceptor
)

请求方法封装

MyRequest类还提供了一个request方法,用于发起网络请求。这个方法接收一个MyRequestConfig类型的参数,并返回一个Promise。在这个方法内部,可以设置单次请求的拦截器,并发起请求。

此外,还提供了一些常用HTTP方法的封装,如get, post, put, delete等,这些方法内部都是调用的request方法。

ts 复制代码
 request<T = any>(config: MyRequestConfig<T>) {
        // 单次拦截器
        if (config.interceptors?.requestSuccessInterceptor) {
            console.log("url请求成功拦截", config.url)
            config = config.interceptors.requestSuccessInterceptor(config as MyInternalRequestConfig)
        }
        return new Promise<T>((resolve, reject) => {
            try {
                // 在尝试发送请求之前,捕获可能的配置错误
                this.instance.request<any, T>(config).then(res => {
                    if (config.interceptors?.responseSuccessInterceptor) {
                        console.log("url响应成功拦截", config.url)
                        res = config.interceptors.responseSuccessInterceptor(res)
                    }
                    resolve(res)
                }).catch(error => {
                    if (config.interceptors?.responseFailInterceptor) {
                        console.log("url响应失败拦截", config.url)
                        error = config.interceptors.responseFailInterceptor(error)
                    }
                    reject(error)
                })
            } catch (e: any) {
                if (config.interceptors?.requestFailInterceptor) {
                    console.log("url请求失败拦截", config.url)
                    const modifiedError = config.interceptors.requestFailInterceptor(e)
                    return Promise.reject(modifiedError)
                } else {
                    return Promise.reject(e)
                }
            }

        })
    }

错误处理

handleErrorMessage方法用于处理错误信息,目前是一个空方法,需要根据实际需求来实现。

类型文件

ts 复制代码
//request/type.ts
import { AxiosError, AxiosRequestConfig, AxiosResponse, InternalAxiosRequestConfig } from 'axios';;

// 针对AxiosRequestConfig进行扩展
export interface MyInterceptors<T = AxiosResponse> {
    requestSuccessInterceptor?: (config: InternalAxiosRequestConfig) => InternalAxiosRequestConfig;
    requestFailInterceptor?: (error: AxiosError) => AxiosError;
    responseSuccessInterceptor?: (res: T) => T;
    responseFailInterceptor?: (error: AxiosError) => AxiosError;
}
export interface MyRequestConfig<T = AxiosResponse> extends AxiosRequestConfig {
    interceptors?: MyInterceptors<T>;
}

export interface MyInternalRequestConfig extends InternalAxiosRequestConfig {
    interceptors?: MyInterceptors;
}

MyInterceptors<T = AxiosResponse>

MyInterceptors接口定义了四个可选的属性,每个属性都是一个函数,用于处理请求或响应的成功或失败情况。这些函数的参数和返回类型与Axios的拦截器保持一致。

  1. requestSuccessInterceptor: 请求成功时执行的函数,接收一个InternalAxiosRequestConfig类型的参数,返回修改后的请求配置。
  2. requestFailInterceptor: 请求失败时执行的函数,接收一个AxiosError类型的参数,返回处理后的错误。
  3. responseSuccessInterceptor: 响应成功时执行的函数,接收一个泛型T类型的参数(默认为AxiosResponse),返回处理后的响应数据。
  4. responseFailInterceptor: 响应失败时执行的函数,接收一个AxiosError类型的参数,返回处理后的错误。

MyRequestConfig<T = AxiosResponse>

MyRequestConfig接口继承自AxiosRequestConfig,在Axios的请求配置基础上进行了扩展。它增加了一个可选的interceptors属性,其类型为MyInterceptors<T>

MyInternalRequestConfig

MyInternalRequestConfig接口继承自InternalAxiosRequestConfig,并增加了一个可选的interceptors属性,其类型为MyInterceptors

创建请求实例

ts 复制代码
import { BASEURL, TIMEOUT } from "./config/carbon";
import MyRequest from "./request";

const myRequest = new MyRequest({
    baseURL: BASEURL,
    timeout: TIMEOUT,
    withCredentials: true,
    headers: {
        "Content-Type": "application/json;charset=utf-8"
    },
    interceptors: {
        requestSuccessInterceptor: (config) => {
            return config
        },
        requestFailInterceptor: (error) => {
            return error;
        },
        responseSuccessInterceptor: (res) => {
            return res
        },
        responseFailInterceptor: (error) => {
            return error;
        }
    }
})

export {
    myRequest,
}

在创建实例时,传入了一个配置对象,其中包含了Axios请求的基本设置和拦截器的定义。

  • baseURL: 设置请求的基础URL。
  • timeout: 设置请求的超时时间。
  • withCredentials: 设置跨域请求是否需要凭证。
  • headers: 设置请求头,这里设置了内容类型为JSON。

另外,定义了四个拦截器:

  1. requestSuccessInterceptor: 请求成功时的拦截器,这里简单地返回了原始的请求配置。
  2. requestFailInterceptor: 请求失败时的拦截器,返回了原始的错误对象。
  3. responseSuccessInterceptor: 响应成功时的拦截器,返回了原始的响应数据。
  4. responseFailInterceptor: 响应失败时的拦截器,返回了原始的错误对象。

通过这种方式,可以确保在整个应用中使用统一的请求配置和处理逻辑,提高代码的可维护性和一致性。

使用

创建api请求函数文件

ts 复制代码
import { myRequest } from "..";


export function getUserList() {
    return myRequest.request({
        url: "/test/user/list",
        method: "GET",
    })
}


// 添加独自的拦截器

export function getUserById(userId: number) {
    return myRequest.get({
        url: `/test/user/${userId}`,
        interceptors: {
            requestSuccessInterceptor: (config) => {
                return config
            },
            requestFailInterceptor: (error) => {
                return error
            },
            responseSuccessInterceptor(res) {
                return res
            },
            responseFailInterceptor(error) {
                return error
            }
        }
    })
}

getUserList函数通过myRequest实例发送一个GET请求,目标URL为"/test/user/list"。这个函数直接使用了myRequest实例的request方法,并传入了请求的配置对象。 getUserById函数通过myRequest实例发送一个GET请求,目标URL为"/test/user/${userId}",其中${userId}是函数参数中传入的用户ID。 这个函数使用了myRequest实例的get方法,并为这个特定请求设置了自己的拦截器。这四个拦截器分别处理请求成功、请求失败、响应成功和响应失败的情况。

在React组件中使用

ts 复制代码
import React, { useEffect } from "react";
import logo from "./logo.svg";
import "./App.css";
import { getUserById, getUserList } from "./service/api/user";

function App() {
  const fetchUserData = async () => {
    const res = await getUserById(1);
    console.log(res);
  };
  useEffect(() => {
    fetchUserData();
  }, []);
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.tsx</code> and save to reload.
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
      </header>
    </div>
  );
}

export default App;

参考自corderwhy老师

相关推荐
y先森1 小时前
CSS3中的伸缩盒模型(弹性盒子、弹性布局)之伸缩容器、伸缩项目、主轴方向、主轴换行方式、复合属性flex-flow
前端·css·css3
前端Hardy1 小时前
纯HTML&CSS实现3D旋转地球
前端·javascript·css·3d·html
susu10830189111 小时前
vue3中父div设置display flex,2个子div重叠
前端·javascript·vue.js
IT女孩儿2 小时前
CSS查缺补漏(补充上一条)
前端·css
吃杠碰小鸡3 小时前
commitlint校验git提交信息
前端
Jacky(易小天)3 小时前
MongoDB比较查询操作符中英对照表及实例详解
数据库·mongodb·typescript·比较操作符
虾球xz4 小时前
游戏引擎学习第20天
前端·学习·游戏引擎
我爱李星璇4 小时前
HTML常用表格与标签
前端·html
疯狂的沙粒4 小时前
如何在Vue项目中应用TypeScript?应该注意那些点?
前端·vue.js·typescript
小镇程序员4 小时前
vue2 src_Todolist全局总线事件版本
前端·javascript·vue.js