Axios多实例封装
Axios多实例封装方案
我将为您提供一个完整的Axios多实例封装方案,包含基础封装、多实例管理和使用示例。
设计思路
- 创建基础axios实例封装,支持请求/响应拦截器
- 实现多实例管理器,支持不同API端点配置
- 提供统一的错误处理机制
- 支持请求取消功能
- 提供完整的类型定义(TypeScript)
实现代码
1. 基础封装和类型定义
// 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. 核心封装类
// 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. 多实例管理器
// 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. 预设实例配置
// 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. 使用示例
// 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组件中使用
// 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;
主要特性
- 多实例支持:可以创建多个Axios实例,每个实例有独立的配置
- 类型安全:完整的TypeScript支持
- 拦截器链:支持请求/响应拦截器,支持异步拦截器
- 错误处理:统一的错误处理机制
- 请求取消:支持取消请求,避免内存泄漏
- 响应转换:支持自定义响应数据转换
- 单例管理:通过ApiManager统一管理所有实例
使用建议
- 根据业务模块划分不同的API实例
- 为不同后端服务创建独立的实例
- 在路由切换时取消未完成的请求
- 使用拦截器统一处理认证、错误提示等通用逻辑
- 根据项目需求扩展配置选项
这个封装方案提供了灵活而强大的Axios多实例管理能力,适合中大型前端项目使用。