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多實例管理能力,適合中大型前端項目使用。