Axios多实例封装

Axios多实例封装方案

我将为您提供一个完整的Axios多实例封装方案,包含基础封装、多实例管理和使用示例。

设计思路

  1. 创建基础axios实例封装,支持请求/响应拦截器
  2. 实现多实例管理器,支持不同API端点配置
  3. 提供统一的错误处理机制
  4. 支持请求取消功能
  5. 提供完整的类型定义(TypeScript)

实现代码

1. 基础封装和类型定义

typescript 复制代码
// types.ts
export interface RequestConfig {
  url: string;
  method?: 'get' | 'post' | 'put' | 'delete' | 'patch';
  data?: any;
  params?: any;
  headers?: Record<string, string>;
  timeout?: number;
  withCredentials?: boolean;
}

export interface ResponseData<T = any> {
  data: T;
  status: number;
  statusText: string;
  headers: any;
  config: RequestConfig;
}

export interface ApiError extends Error {
  config: RequestConfig;
  code?: string;
  request?: any;
  response?: ResponseData;
}

export interface ApiInstance {
  request: <T = any>(config: RequestConfig) => Promise<ResponseData<T>>;
  get: <T = any>(url: string, params?: any, config?: Omit<RequestConfig, 'url' | 'method' | 'data'>) => Promise<ResponseData<T>>;
  post: <T = any>(url: string, data?: any, config?: Omit<RequestConfig, 'url' | 'method' | 'params'>) => Promise<ResponseData<T>>;
  put: <T = any>(url: string, data?: any, config?: Omit<RequestConfig, 'url' | 'method' | 'params'>) => Promise<ResponseData<T>>;
  delete: <T = any>(url: string, params?: any, config?: Omit<RequestConfig, 'url' | 'method' | 'data'>) => Promise<ResponseData<T>>;
  patch: <T = any>(url: string, data?: any, config?: Omit<RequestConfig, 'url' | 'method' | 'params'>) => Promise<ResponseData<T>>;
}

export interface ApiInstanceConfig {
  baseURL?: string;
  timeout?: number;
  headers?: Record<string, string>;
  withCredentials?: boolean;
  transformRequest?: (config: RequestConfig) => RequestConfig;
  transformResponse?: (response: ResponseData) => ResponseData;
  requestInterceptors?: Array<(config: RequestConfig) => RequestConfig | Promise<RequestConfig>>;
  responseInterceptors?: Array<(response: ResponseData) => ResponseData | Promise<ResponseData>>;
  errorInterceptors?: Array<(error: ApiError) => Promise<never>>;
}

2. 核心封装类

typescript 复制代码
// axios-instance.ts
import axios, { AxiosRequestConfig, AxiosResponse, AxiosError, CancelTokenSource } from 'axios';
import {
  RequestConfig,
  ResponseData,
  ApiError,
  ApiInstance,
  ApiInstanceConfig
} from './types';

export class AxiosInstance implements ApiInstance {
  private instance: any;
  private cancelTokenSource: CancelTokenSource;

  constructor(config: ApiInstanceConfig = {}) {
    this.cancelTokenSource = axios.CancelToken.source();
    
    const axiosConfig: AxiosRequestConfig = {
      baseURL: config.baseURL,
      timeout: config.timeout || 30000,
      headers: {
        'Content-Type': 'application/json',
        ...config.headers
      },
      withCredentials: config.withCredentials || false,
      cancelToken: this.cancelTokenSource.token
    };

    this.instance = axios.create(axiosConfig);

    // 请求拦截器
    this.instance.interceptors.request.use(
      (axiosConfig: AxiosRequestConfig) => {
        let finalConfig: RequestConfig = {
          url: axiosConfig.url || '',
          method: axiosConfig.method as any,
          data: axiosConfig.data,
          params: axiosConfig.params,
          headers: axiosConfig.headers,
          timeout: axiosConfig.timeout,
          withCredentials: axiosConfig.withCredentials
        };

        // 应用自定义请求转换
        if (config.transformRequest) {
          finalConfig = config.transformRequest(finalConfig);
        }

        // 应用请求拦截器
        if (config.requestInterceptors) {
          for (const interceptor of config.requestInterceptors) {
            finalConfig = interceptor(finalConfig);
          }
        }

        return {
          ...axiosConfig,
          ...finalConfig
        };
      },
      (error: AxiosError) => {
        const apiError: ApiError = {
          name: 'RequestError',
          message: error.message,
          config: error.config as RequestConfig,
          code: error.code,
          request: error.request
        };
        return Promise.reject(apiError);
      }
    );

    // 响应拦截器
    this.instance.interceptors.response.use(
      (response: AxiosResponse) => {
        const responseData: ResponseData = {
          data: response.data,
          status: response.status,
          statusText: response.statusText,
          headers: response.headers,
          config: response.config as RequestConfig
        };

        let finalResponse = responseData;

        // 应用响应拦截器
        if (config.responseInterceptors) {
          for (const interceptor of config.responseInterceptors) {
            finalResponse = interceptor(finalResponse);
          }
        }

        // 应用自定义响应转换
        if (config.transformResponse) {
          finalResponse = config.transformResponse(finalResponse);
        }

        return finalResponse;
      },
      (error: AxiosError) => {
        const apiError: ApiError = {
          name: 'ResponseError',
          message: error.message,
          config: error.config as RequestConfig,
          code: error.code,
          request: error.request,
          response: error.response ? {
            data: error.response.data,
            status: error.response.status,
            statusText: error.response.statusText,
            headers: error.response.headers,
            config: error.response.config as RequestConfig
          } : undefined
        };

        // 应用错误拦截器
        if (config.errorInterceptors) {
          for (const interceptor of config.errorInterceptors) {
            return interceptor(apiError);
          }
        }

        return Promise.reject(apiError);
      }
    );
  }

  async request<T = any>(config: RequestConfig): Promise<ResponseData<T>> {
    try {
      return await this.instance.request(config);
    } catch (error) {
      throw error as ApiError;
    }
  }

  get<T = any>(url: string, params?: any, config?: Omit<RequestConfig, 'url' | 'method' | 'data'>): Promise<ResponseData<T>> {
    return this.request<T>({
      url,
      method: 'get',
      params,
      ...config
    });
  }

  post<T = any>(url: string, data?: any, config?: Omit<RequestConfig, 'url' | 'method' | 'params'>): Promise<ResponseData<T>> {
    return this.request<T>({
      url,
      method: 'post',
      data,
      ...config
    });
  }

  put<T = any>(url: string, data?: any, config?: Omit<RequestConfig, 'url' | 'method' | 'params'>): Promise<ResponseData<T>> {
    return this.request<T>({
      url,
      method: 'put',
      data,
      ...config
    });
  }

  delete<T = any>(url: string, params?: any, config?: Omit<RequestConfig, 'url' | 'method' | 'data'>): Promise<ResponseData<T>> {
    return this.request<T>({
      url,
      method: 'delete',
      params,
      ...config
    });
  }

  patch<T = any>(url: string, data?: any, config?: Omit<RequestConfig, 'url' | 'method' | 'params'>): Promise<ResponseData<T>> {
    return this.request<T>({
      url,
      method: 'patch',
      data,
      ...config
    });
  }

  // 取消当前实例的所有请求
  cancelAllRequests(message?: string): void {
    this.cancelTokenSource.cancel(message || 'Request canceled');
    // 创建新的cancel token用于后续请求
    this.cancelTokenSource = axios.CancelToken.source();
    this.instance.defaults.cancelToken = this.cancelTokenSource.token;
  }

  // 获取当前cancel token,可用于特定请求的取消
  getCancelToken() {
    return this.cancelTokenSource.token;
  }
}

3. 多实例管理器

typescript 复制代码
// api-manager.ts
import { AxiosInstance } from './axios-instance';
import { ApiInstanceConfig, ApiInstance } from './types';

export class ApiManager {
  private instances: Map<string, AxiosInstance> = new Map();
  private static instance: ApiManager;

  private constructor() {}

  static getInstance(): ApiManager {
    if (!ApiManager.instance) {
      ApiManager.instance = new ApiManager();
    }
    return ApiManager.instance;
  }

  createInstance(name: string, config: ApiInstanceConfig = {}): AxiosInstance {
    if (this.instances.has(name)) {
      throw new Error(`API instance with name "${name}" already exists`);
    }

    const instance = new AxiosInstance(config);
    this.instances.set(name, instance);
    return instance;
  }

  getInstance(name: string): AxiosInstance {
    const instance = this.instances.get(name);
    if (!instance) {
      throw new Error(`API instance with name "${name}" not found`);
    }
    return instance;
  }

  removeInstance(name: string): void {
    this.instances.delete(name);
  }

  hasInstance(name: string): boolean {
    return this.instances.has(name);
  }

  getAllInstanceNames(): string[] {
    return Array.from(this.instances.keys());
  }

  // 取消所有实例的所有请求
  cancelAllRequests(message?: string): void {
    this.instances.forEach(instance => {
      instance.cancelAllRequests(message);
    });
  }
}

// 导出单例
export const apiManager = ApiManager.getInstance();

4. 预设实例配置

typescript 复制代码
// api-configs.ts
import { ApiInstanceConfig } from './types';

// 认证拦截器
const authRequestInterceptor = (config: any) => {
  const token = localStorage.getItem('auth_token');
  if (token) {
    config.headers = config.headers || {};
    config.headers.Authorization = `Bearer ${token}`;
  }
  return config;
};

// 通用响应拦截器 - 处理标准响应格式
const standardResponseInterceptor = (response: any) => {
  // 假设后端返回格式为 { code: number, data: any, message: string }
  if (response.data && typeof response.data === 'object') {
    if (response.data.code !== 0 && response.data.code !== 200) {
      throw new Error(response.data.message || 'Request failed');
    }
    // 返回数据部分
    return {
      ...response,
      data: response.data.data
    };
  }
  return response;
};

// 错误处理拦截器
const errorInterceptor = (error: any) => {
  if (error.response) {
    switch (error.response.status) {
      case 401:
        // 处理未授权
        localStorage.removeItem('auth_token');
        window.location.href = '/login';
        break;
      case 403:
        // 处理禁止访问
        console.error('Permission denied');
        break;
      case 500:
        // 处理服务器错误
        console.error('Server error');
        break;
      default:
        console.error('Request error', error.message);
    }
  } else if (error.request) {
    console.error('Network error', error.message);
  } else {
    console.error('Request setup error', error.message);
  }
  return Promise.reject(error);
};

// 主API配置
export const mainApiConfig: ApiInstanceConfig = {
  baseURL: process.env.REACT_APP_API_BASE_URL || 'https://api.example.com',
  timeout: 30000,
  headers: {
    'X-Requested-With': 'XMLHttpRequest'
  },
  requestInterceptors: [authRequestInterceptor],
  responseInterceptors: [standardResponseInterceptor],
  errorInterceptors: [errorInterceptor]
};

// 认证API配置
export const authApiConfig: ApiInstanceConfig = {
  baseURL: process.env.REACT_APP_AUTH_API_URL || 'https://auth.example.com',
  timeout: 15000,
  headers: {
    'X-API-Source': 'web-frontend'
  },
  responseInterceptors: [standardResponseInterceptor],
  errorInterceptors: [errorInterceptor]
};

// 第三方API配置
export const thirdPartyApiConfig: ApiInstanceConfig = {
  baseURL: 'https://api.thirdparty.com',
  timeout: 20000,
  transformResponse: (response) => {
    // 转换第三方API的响应格式
    if (response.data && response.data.result) {
      return {
        ...response,
        data: response.data.result
      };
    }
    return response;
  }
};

5. 使用示例

typescript 复制代码
// api-instances.ts
import { apiManager } from './api-manager';
import { mainApiConfig, authApiConfig, thirdPartyApiConfig } from './api-configs';

// 创建多个API实例
export const mainApi = apiManager.createInstance('main', mainApiConfig);
export const authApi = apiManager.createInstance('auth', authApiConfig);
export const thirdPartyApi = apiManager.createInstance('thirdParty', thirdPartyApiConfig);

// 业务API函数
export const userApi = {
  // 获取用户信息
  getUser: (userId: number) => mainApi.get(`/users/${userId}`),
  
  // 更新用户信息
  updateUser: (userId: number, data: any) => mainApi.put(`/users/${userId}`, data),
  
  // 获取用户列表
  getUsers: (params?: { page: number; limit: number }) => mainApi.get('/users', params)
};

export const authService = {
  // 登录
  login: (credentials: { email: string; password: string }) => 
    authApi.post('/login', credentials),
  
  // 注册
  register: (userData: any) => authApi.post('/register', userData),
  
  // 退出登录
  logout: () => authApi.post('/logout')
};

export const thirdPartyService = {
  // 获取第三方数据
  getData: (id: string) => thirdPartyApi.get(`/data/${id}`),
  
  // 提交数据到第三方
  submitData: (data: any) => thirdPartyApi.post('/submit', data)
};

// 取消所有请求(例如在路由切换时)
export const cancelAllRequests = (message?: string) => {
  apiManager.cancelAllRequests(message);
};

6. 在React组件中使用

typescript 复制代码
// UserProfile.tsx
import React, { useState, useEffect } from 'react';
import { userApi, cancelAllRequests } from './api-instances';

const UserProfile: React.FC<{ userId: number }> = ({ userId }) => {
  const [user, setUser] = useState<any>(null);
  const [loading, setLoading] = useState<boolean>(true);
  const [error, setError] = useState<string>('');

  useEffect(() => {
    const fetchUser = async () => {
      try {
        setLoading(true);
        setError('');
        const response = await userApi.getUser(userId);
        setUser(response.data);
      } catch (err: any) {
        setError(err.message || 'Failed to fetch user');
      } finally {
        setLoading(false);
      }
    };

    fetchUser();

    // 组件卸载时取消请求
    return () => {
      cancelAllRequests('Component unmounted');
    };
  }, [userId]);

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error}</div>;

  return (
    <div>
      <h1>{user.name}</h1>
      <p>Email: {user.email}</p>
      {/* 更多用户信息 */}
    </div>
  );
};

export default UserProfile;

主要特性

  1. 多实例支持:可以创建多个Axios实例,每个实例有独立的配置
  2. 类型安全:完整的TypeScript支持
  3. 拦截器链:支持请求/响应拦截器,支持异步拦截器
  4. 错误处理:统一的错误处理机制
  5. 请求取消:支持取消请求,避免内存泄漏
  6. 响应转换:支持自定义响应数据转换
  7. 单例管理:通过ApiManager统一管理所有实例

使用建议

  1. 根据业务模块划分不同的API实例
  2. 为不同后端服务创建独立的实例
  3. 在路由切换时取消未完成的请求
  4. 使用拦截器统一处理认证、错误提示等通用逻辑
  5. 根据项目需求扩展配置选项

这个封装方案提供了灵活而强大的Axios多实例管理能力,适合中大型前端项目使用。