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老师

相关推荐
森叶2 分钟前
Electron 安装包 asar 解压定位问题实战
前端·javascript·electron
drebander6 分钟前
ubuntu 安装 chrome 及 版本匹配的 chromedriver
前端·chrome
软件技术NINI15 分钟前
html知识点框架
前端·html
深情废杨杨19 分钟前
前端vue-插值表达式和v-html的区别
前端·javascript·vue.js
GHUIJS19 分钟前
【vue3】vue3.3新特性真香
前端·javascript·vue.js
众生回避25 分钟前
鸿蒙ms参考
前端·javascript·vue.js
洛千陨25 分钟前
Vue + element-ui实现动态表单项以及动态校验规则
前端·vue.js
GHUIJS1 小时前
【vue3】vue3.5
前端·javascript·vue.js
&白帝&2 小时前
uniapp中使用picker-view选择时间
前端·uni-app
魔术师卡颂2 小时前
如何让“学源码”变得轻松、有意义
前端·面试·源码