Axios使用ts的class封装

995 阅读4分钟

背景

参考很多文章,基本上使用ts对axios进行类封装的文章很少,所以想着使用ts进行类封装,希望可以给大家一点帮助。本次使用axios的版本为v1.3.3,Github地址

本次封装有如下功能:

  1. 配置不同AxiosRequestConfig请求不同的接口地址
  2. 接口失败返回固定数据,不需要进行try...catch捕获
  3. 通过配置,直接显示成功失败提示
  4. 单个接口可以进行特定的请求拦截
  5. 接口重复请求取消功能

目录结构如下

request
├─index.ts // 对外暴露的函数
├─request.ts   // 封装类
├─types
|   └index.ts // 类型定义

1、项目中通过配置请求多个接口

在一个项目中通常存在一个接口地址,如果存在多个接口地址,通常会将axios的常规封装写两次,进行使用。

1.1 封装一个axios类

类中的的请求和响应拦截,都是使用传入的拦截器,目的是方便多个接口地址进行配置。

// request.ts 文件
import axios from 'axios'
import type { AxiosError, AxiosResponse, AxiosInstance } from 'axios'
import type { RequestConfig, RequestInterceptors } from './types'

class Request {
    private instance: AxiosInstance
    private interceptorsObj?: RequestInterceptors<AxiosResponse>

    constructor(config: RequestConfig) {
        // 1.创建axios实例
        this.instance = axios.create(config)
        // 2.传入拦截器
        this.interceptorsObj = config.interceptors

        // 3.此处为了多个config传入不同的拦截器
        this.instance.interceptors.request.use(
                this.interceptorsObj?.requestInterceptors,
                this.interceptorsObj?.requestInterceptorsCatch
        )
        this.instance.interceptors.response.use(
                this.interceptorsObj?.responseInterceptors,
                this.interceptorsObj?.responseInterceptorsCatch
        )
    }
    
    public request<T>(config: RequestConfig<T>): Promise<T> {
	return new Promise((resolve, reject) => {
            // 单个请求设置拦截器在这里
            if (config.interceptors?.requestInterceptors) { 
                this.instance.interceptors.request.use(config.interceptors.requestInterceptor)
            }
            this.instance
                .request<any, T>(config)
                .then(res => {
                // 接口数据响应处理
                    if (config.interceptors?.responseInterceptors) {
                        res = config.interceptors.responseInterceptors(res)
                    }
                    resolve(res)
                })
                .catch((err: AxiosError) => {
                // 向上抛出错误
                    reject(err)
                })
            })
	}
}

export default Request

1.2 类型定义:

定义好基础的拦截器后,我们需要改造我们传入的参数的类型,因为axios提供的AxiosRequestConfig是不允许我们传入拦截器的,所以说我们自定义了RequestConfig,让其继承与AxiosRequestConfig 


import type { AxiosError, AxiosRequestConfig, AxiosResponse, InternalAxiosRequestConfig } from 'axios'

export interface RequestInterceptors<T> {
	//请求拦截
	requestInterceptors?: (config: InternalAxiosRequestConfig) => InternalAxiosRequestConfig
	requestInterceptorsCatch?: (err: AxiosError) => any

	//响应拦截
	responseInterceptors?: (response: T) => T
	responseInterceptorsCatch?: (err: AxiosError) => any
}

export interface RequestConfig<T = AxiosResponse> extends AxiosRequestConfig {
	interceptors?: RequestInterceptors<T>
        // 用于接口成功失败的提示文案
	desc?: string 
}


// 返回参数定义
export interface IBaseRes<T = any> {
	code: number
	data: T
	msg: string
	err: boolean
}

// 将请求包一层 转换成 请求 响应的方式
export interface IReq<P, T> extends RequestConfig<IBaseRes<T>> {
	data?: P
	// 添加属性 默认post,delete,put返回
	desc?: string
}

// 将可选的部分参数转换成必填
export type CustomRequired<T, K extends keyof T> = {
	[P in K]-?: T[P]
} & Omit<T, K>


1.3 对外暴露函数

使用高阶函数,第一层传递接口基本信息,第二层接受具体接口的入参和出参。错误时,返回code为0的响应数据,这样即使接口错误,不需要进行try...catch。只需要判断err是否为true

// index.ts
import Request from './request'
import type { IBaseRes, RequestConfig, IReq } from './types'

export default (config: RequestConfig) => {
    const api = new Request(config)
    return function <K = any, T = any>(config: IReq<K, T>): Promise<IBaseRes<T>> {
        return new Promise((resolve, reject) => {
            const { method = 'GET' } = config
            if (method === 'get' || method === 'GET') {
                config.params = config.data
                delete config.data
            }
            return api
                .request<IBaseRes<T>>(config)
                .then(res => {
                    resolve({ ...res, err: false })
                })
                .catch(err => {
                    resolve({ code: 0, data: null as T, msg: 'fail', err: true })
                })
            })
    }
}


1.4 使用

通过config 文件配置不同的接口地址,导出接口函数。

在全局的响应拦截中,显示成功失败提示。 可以添加接口重复请求取消功能,参考接口取消功能

// config.ts
import type { RequestConfig, CustomRequired, IBaseRes } from '../request/types'
import type { AxiosResponse, AxiosError } from 'axios'
import setApiConfig from '../request'
import abort from '@sunrisecn/axios-abort'
import { notification } from 'ant-design-vue'

const config: CustomRequired<RequestConfig, 'baseURL'> = {
    baseURL: 'http://localhost:3000',
    timeout: 1000 * 60 * 5,
    interceptors: {
        requestInterceptors: config => {
            config.signal = abort.add({ url: config.url!, method: config.method! })
            abort.judge({ url: config.url!, method: config.method! })
            return config
        },
        requestInterceptorsCatch: error => {
            return Promise.reject(error)
        },
        responseInterceptors: response => {
        // 添加提示
            notify.onFulfilled(response)
            abort.remove({ url: response.config.url!, method: response.config.method! })
            return response
        },
        responseInterceptorsCatch: error => {
        // 错误提示
            notify.onRejected(error)
            return Promise.reject(error)
        }
    }
}

const config1: CustomRequired<RequestConfig, 'baseURL'> = {
    baseURL: 'http://localhost:8000',
    timeout: 1000 * 60 * 5,
    interceptors: {
        requestInterceptors: config => {
            return config
        },
        requestInterceptorsCatch: error => {
            return Promise.reject(error)
        },
        responseInterceptors: response => {
            return response
        },
        responseInterceptorsCatch: error => {
            return Promise.reject(error)
        }
    }
}
// 接口成功或失败的提示
const notify = {
    onFulfilled: (response: AxiosResponse<IBaseRes>) => {
        const { code, msg } = response.data
        const { desc, method } = response.config as RequestConfig
        // 如果desc被定义,则执行反馈逻辑
        // 判断code码 1是成功
        if (code === 1 && desc) {
            // 判断是否需要显示
            notification.success({
                message: `${desc}成功`
            })
            // 是 0 且需要失败提醒 才展示
        } else if (code === 0) {
            notification.error({
                message: `${desc}错误`,
                description: `原因:${msg}`
            })
        }
    },
    onRejected: (error: AxiosError) => {
        const { response, config } = error
        // 对4xx,5xx状态码做失败反馈
        const { url, desc } = config as RequestConfig
        if (!desc) {
            return Promise.reject(error)
        }
        if (response?.status && response?.statusText) {
            notification.error({
                message: `${desc}错误`,
                description: `状态:${response.status}~${response.statusText}
                路径:${url}`                        
            })
        } else {
            // 处理请求响应失败,例如网络offline,超时等做失败反馈
            notification.error({
                message: `${desc}失败`,
                description: `原因:${error.message}路径:${url}`
            })
        }
    }
}

export const request = setApiConfig(config)
export const request1 = setApiConfig(config1)

api文件中使用,此处可以进行配置单个接口的请求和响应拦截。

import { request } from './config'
interface IP {
	name: string
	age: number
}

export const getList = (data: IP) =>
    request<IP, number>({
        url: '/getList',
        method: 'get',
        data,
        desc: '获取列表成功',
        interceptors: {
            requestInterceptors: config => {
                console.log('打印***单个接口请求拦截', config)
                return config
            },
            responseInterceptors: res => {
                if (res.data) {
                    console.log('打印***单个接口响应拦截', res)
                }
                return res
            }
        }
    })

参考文章:

# 在项目中用ts封装axios

# 设计一套实用的API层架构