axios二次封装ts版

5 阅读2分钟

1. 依赖安装

# 安装依赖
pnpm install axios -S

2. 直接上代码

1. axios.ts
import { CancelTokenItem, HttpAxiosConfig, HttpResponseData } from '@/domain/axios';
import axios, { AxiosError, AxiosInstance, AxiosResponse, InternalAxiosRequestConfig } from 'axios';
import { sleep } from '@/utils';

export class HttpAxios {
    /**
     * axios实例
     */
    instance: AxiosInstance;

    /**
     * 请求方式
     */
    method: string = 'post';
    /**
     * 超时时间
     */
    timeout: number = 300 * 1000;
    /**
     * loading实例
     */
    loadingInstance: any = null;
    /**
     * 当前正在进行的请求计数器,用于控制loading的显示和隐藏
     */
    requestCount: number = 0;
    /**
     * axios cancelToken 数组,方便处理取消请求
     */
    cancelTokenArr: CancelTokenItem[] = [];
    /**
     * 是否在切换页面时清除请求
     */
    clearFlag = true;

    constructor(config: HttpAxiosConfig = {}) {
        let instance = axios.create(config);
        // 请求拦截
        instance.interceptors.request.use(
            (config: InternalAxiosRequestConfig) => this.onBeforeRequest(config),
            (config: InternalAxiosRequestConfig) => this.onRequestRejected(config)
        );
        // 响应拦截
        instance.interceptors.response.use(
            (response: any) => this.onResponseResolved(response),
            (error: any) => this.onResponseRejected(error)
        );

        this.instance = instance;
    }

    /**
     * 显示loading
     * @param config 请求配置
     */
    private showLoading(config: HttpAxiosConfig) {
        if (config.showLoading && !this.loadingInstance) {
        }
        this.requestCount++;
    }
    /**
     * 隐藏loading
     */
    private hideLoading() {
        this.requestCount--;
        if (this.requestCount === 0 && this.loadingInstance) {
            // this.loadingInstance.close();
            this.loadingInstance = null;
        }
    }

    /**
     * 请求拦截,处理header部分传值及是否显示loading
     * @param config
     */
    onBeforeRequest = (config: InternalAxiosRequestConfig): InternalAxiosRequestConfig => {
        // 显示loading
        this.showLoading(config);
        let _config = {
            withCredentials: false, // 处理跨域问题
            timeout: this.timeout,
        };
        if (this.method == 'formate') {
            config.headers.setContentType('application/x-www-form-urlencoded; charset=UTF-8');
        }
        config.cancelToken = new axios.CancelToken((cancel) => {
            // 添加url,方便取消特定url的请求
            this.cancelTokenArr.push({ cancel, url: config.url || '' });
        });
        return Object.assign({}, config, _config);
    };

    /**
     * 请求拦截失败
     * @param error
     */
    onRequestRejected = (error: any): Promise<any> => {
        return Promise.reject(error);
    };

    /**
     * 返回拦截
     * @param response
     * @returns
     */
    onResponseResolved = async (response: AxiosResponse) => {
        let responseData: HttpResponseData<any> = response.data || {};
        // 判断返回成功码
        const { code, data } = responseData;
        if (code === 200) {
            return data || '';
        }
        await this.retryRequest(response.config);
        this.hideLoading();
        return undefined;
    };

    /**
     * 重试请求
     * @param config
     */
    retryRequest = async (config: HttpAxiosConfig) => {
        // 添加重试逻辑
        // 只有在有重试次数时,才回进行重试
        config.retryCount = config.retryCount ?? 0;
        if (config && config.maxRetries && config.retryCount < config.maxRetries) {
            config.retryCount = config.retryCount + 1;
            await sleep(config.retryDelay || 1000); // 重试前等待1秒
            return this.instance(config);
        }
    };

    /**
     * 处理请求错误时情况 根据不同的状态码
     * @param error
     */
    onResponseRejected = async (error: AxiosError) => {
        const { config = {}, message } = error;
        await this.retryRequest(config ?? {});
        // 隐藏loading
        if (this.loadingInstance) {
            this.loadingInstance.close();
            this.loadingInstance = null;
        }
        this.hideLoading();
        if (/timeout/g.test(message)) {
            return Promise.reject(error);
        }
        return Promise.reject(error);
    };

    /**
     * 发送请求
     * @param url
     * @param params
     * @param method
     * @param config
     * @returns
     */
    sendRequest = (url: string, params: Record<string, any> = {}, method: string = 'post', config: HttpAxiosConfig = {}) => {
        if (!this.instance) {
            return;
        }
        this.method = method;
        if (config?.maxRetries) {
            config.retryCount = 0;
        }
        const _method = method.toLocaleLowerCase();
        if (_method === 'get') {
            params = {
                params: params,
            };
            return this.instance.get(url, { ...params, ...config });
        }
        if (_method === 'formdata') {
            let reqData = new FormData();
            for (let key in params) {
                reqData.append(key, params[key]);
            }
            return this.instance.post(url, reqData, config);
        }
        return this.instance.post(url, params, config);
    };

    /**
     * 取消请求
     * @param url
     */
    async cancelRequest(url: string) {
        if (this.cancelTokenArr.length === 0) {
            return;
        }
        for (let i = 0; i < this.cancelTokenArr.length; i++) {
            if (this.cancelTokenArr[i].url === url) {
                this.cancelTokenArr[i].cancel();
                this.cancelTokenArr.splice(i, 1);
                break;
            }
        }
    }
    /**
     * 清除axios 请求
     */
    async clearRequests() {
        if (this.cancelTokenArr.length === 0 || !this.clearFlag) {
            return;
        }
        this.cancelTokenArr.forEach((ele) => {
            ele.cancel();
        });
        this.cancelTokenArr = [];
    }
}
export const getHttpAxiosInstance = () => {
    let instance: HttpAxios | null = null;
    return () => {
        if (!instance) {
            instance = new HttpAxios({});
        }
        return instance;
    };
};
2. 生成axios instance,并将api Maps转换为对应的请求函数
import { AxiosSendRequestParam, HttpAxiosConfig } from '@/domain/axios';
import { getHttpAxiosInstance, HttpAxios } from './axios';
import demo from './demo';

const httpAxios: HttpAxios = getHttpAxiosInstance()();

/**
 * 映射请求方法
 * @param apiMaps
 * @returns
 */
const maps2Method = (apiMaps: Record<string, AxiosSendRequestParam>) => {
    let result: Record<string, any> = {};
    for (let key in apiMaps) {
        const { method, url, params, config } = apiMaps[key];
        result[key] = (_params: Record<string, any>, _config: HttpAxiosConfig) => {
            return httpAxios.sendRequest(url, { ...params, ..._params }, method, { ...config, ..._config });
        };
    }
    return result;
};

export default {
    ...maps2Method({
        ...demo,
    }),
};
3. 用到的axios中相关模型
import { AxiosHeaders, AxiosRequestConfig, Canceler, Method } from 'axios';

export interface HttpAxiosConfig extends AxiosRequestConfig {
    showLoading?: boolean;
    noAuth?: boolean;
    maxRetries?: number;
    retryDelay?: number;
    retryCount?: number;
}

export interface AxiosSendRequestParam {
    method: Method;
    url: string;
    params?: Record<string, any>;
    headers?: AxiosHeaders;
    config?: HttpAxiosConfig;
}

export interface ApiMaps {
    [propName: string]: AxiosSendRequestParam;
}

export type CancelTokenItem = {
    cancel: Canceler;
    url: string;
};

export interface HttpResponseData<T> {
    code: number;
    data: T;
    message: string;
}
4. demo.ts
import { AxiosSendRequestParam } from "@/domain/axios"

const demo: Record<string, AxiosSendRequestParam> = {
    demo: {
        method: 'post',
        url: '/api/demo',
    }
}

export default demo;
5. 在vue中使用
<script setup lang="ts">
import api from '@/api';

const request = async (): Promise<void> => {
  const res = await api.demo();
  console.log(res);
}

request();
</script>