2026-03-26
JavaScript
0

目录

告别臃肿 Axios 封装!打造 Vue 风格插件化请求管理工具
📘 前言
🎯 设计目标
📁 目录
🔧 一、核心实现:createAxios 插件管理器
📝 二、插件编写规范
🧩 三、内置七大实用插件
🚀 四、使用示例(链式调用)
⚡️ 五、预设模式(一行创建实例)
📦 六、完整源码

告别臃肿 Axios 封装!打造 Vue 风格插件化请求管理工具

📘 前言

在前端项目开发中,Axios 几乎是标配 HTTP 请求库。 为了实现鉴权、日志、错误处理、重复请求取消、接口重试、缓存等能力,我们几乎都会对 Axios 进行二次封装。

但传统封装普遍存在这些痛点:

  • 所有逻辑耦合在一个文件,代码臃肿
  • 新增/删除功能要改多处,维护成本高
  • 功能混杂,可读性差
  • 无法灵活组合能力,复用性差

今天给大家带来一套模仿 Vue 插件机制的 Axios 插件化封装createAxios().use(插件1).use(插件2) 真正做到:解耦、可插拔、高复用、易维护、生产可用。


🎯 设计目标

  • 模仿 Vue 插件机制,使用 .use() 链式调用
  • 每个功能独立成插件,互不影响
  • 支持插件配置、防重复安装、异常捕获
  • 内置高频通用插件,开箱即用
  • 支持预设模式,一行创建完整请求实例

📁 目录

  1. 核心实现:createAxios 插件管理器
  2. 插件编写规范
  3. 内置七大实用插件
  4. 使用示例(链式调用)
  5. 预设模式(极简/默认/全量)
  6. 完整源码(可直接复制使用)

🔧 一、核心实现:createAxios 插件管理器

这是整个架构的灵魂,负责创建 Axios 实例、管理插件生命周期。

javascript
import axios from 'axios'; export function createAxios(config = {}) { const instance = axios.create(config); const installedPlugins = new Set(); const pluginOrder = []; function use(plugin, options = {}) { if (typeof plugin === 'function') { plugin = { install: plugin }; } if (!plugin || typeof plugin.install !== 'function') { console.error('[AxiosUse] 插件必须提供 install 方法'); return instance; } if (installedPlugins.has(plugin)) { console.warn('[AxiosUse] 插件已安装,跳过'); return instance; } installedPlugins.add(plugin); try { plugin.install(instance, options); console.log(`[AxiosUse] ✅ 插件安装成功: ${plugin.name || 'anonymous'}`); } catch (error) { console.error('[AxiosUse] ❌ 插件安装失败', error); } return instance; } function useBatch(plugins) { plugins.forEach(({ plugin, options = {} }) => { use(plugin, options); }); return instance; } instance.use = use; instance.useBatch = useBatch; instance.getInstalledPlugins = () => [...pluginOrder]; instance.hasPlugin = (plugin) => installedPlugins.has(plugin); return instance; }

📝 二、插件编写规范

和 Vue 插件完全一致,必须提供 install 方法。

const MyPlugin = { name: 'MyPlugin', install(axiosInstance, options) { axiosInstance.interceptors.request.use(config => { return config; }); } }; const http = createAxios().use(MyPlugin);

🧩 三、内置七大实用插件

  1. LogPlugin 请求 / 响应 / 错误 / 耗时日志,开发调试神器
  2. TokenPlugin 自动携带 Token,支持白名单、自定义请求头
  3. CancelDuplicatePlugin 取消重复请求,防止重复点击、接口竞态
  4. RetryPlugin 指数退避重试,弱网环境必备
  5. LoadingPlugin 自动管理全局 Loading,计数模式防闪烁
  6. CachePlugin GET 请求缓存(LRU 策略),提升页面性能
  7. ErrorHandlerPlugin 统一错误处理,状态码映射 + 全局提示

🚀 四、使用示例(链式调用)

const http = createAxios({ baseURL: '/api' }) .use(LogPlugin) .use(TokenPlugin, { getToken: () => localStorage.getItem('token'), whiteList: ['/login', '/register'] }) .use(CancelDuplicatePlugin) .use(RetryPlugin, { maxRetries: 3 }) .use(LoadingPlugin) .use(CachePlugin, { ttl: 5 * 60 * 1000 }) .use(ErrorHandlerPlugin);

⚡️ 五、预设模式(一行创建实例)

import { createAxiosWithPreset } from '@/utils/axios'; // 极简模式 const httpMin = createAxiosWithPreset('minimal'); // 默认模式 const httpDefault = createAxiosWithPreset('default'); // 全量模式 const httpFull = createAxiosWithPreset('full');

📦 六、完整源码

/** * Axios 插件管理工具 - 类似 Vue 的 use 链式调用风格 */ import axios from 'axios'; export function createAxios(config = {}) { const instance = axios.create(config); const installedPlugins = new Set(); const pluginOrder = []; function use(plugin, options = {}) { if (typeof plugin === 'function') { plugin = { install: plugin }; } if (!plugin || typeof plugin.install !== 'function') { console.error('[AxiosUse] 插件必须提供 install 方法:', plugin); return instance; } if (installedPlugins.has(plugin)) { console.warn('[AxiosUse] 插件已安装,跳过:', plugin.name || 'anonymous'); return instance; } installedPlugins.add(plugin); const pluginInfo = { name: plugin.name || 'anonymous', options, installTime: Date.now() }; pluginOrder.push(pluginInfo); try { plugin.install(instance, options); console.log(`[AxiosUse] ✅ 插件安装成功: ${pluginInfo.name}`); } catch (error) { console.error(`[AxiosUse] ❌ 插件安装失败: ${pluginInfo.name}`, error); } return instance; } function useBatch(plugins) { plugins.forEach(({ plugin, options = {} }) => { use(plugin, options); }); return instance; } instance.use = use; instance.useBatch = useBatch; instance.getInstalledPlugins = () => [...pluginOrder]; instance.hasPlugin = (plugin) => installedPlugins.has(plugin); return instance; } // ==================== 内置插件 ==================== export const LogPlugin = { name: 'LogPlugin', install(instance, options = {}) { const { logRequest = true, logResponse = true, logError = true, prefix = '[Axios]' } = options; instance.interceptors.request.use( (config) => { config._startTime = Date.now(); logRequest && console.log(`${prefix} 📤 ${config.method?.toUpperCase()} ${config.url}`); return config; }, (error) => { logError && console.error(`${prefix} ❌ 请求错误:`, error.message); return Promise.reject(error); } ); instance.interceptors.response.use( (response) => { const duration = Date.now() - response.config._startTime; logResponse && console.log(`${prefix} 📥 ${response.config.url} - ${response.status} (${duration}ms)`); return response; }, (error) => { if (logError) { const duration = Date.now() - (error.config?._startTime || Date.now()); console.error( `${prefix} ❌ ${error.config?.url} - ${error.response?.status || 'Network Error'} (${duration}ms)` ); } return Promise.reject(error); } ); } }; export const TokenPlugin = { name: 'TokenPlugin', install(instance, options = {}) { const { getToken = () => localStorage.getItem('token'), headerName = 'Authorization', tokenPrefix = 'Bearer ', whiteList = [] } = options; instance.interceptors.request.use( (config) => { if (whiteList.some(url => config.url?.includes(url))) { return config; } const token = getToken(); if (token) { config.headers[headerName] = `${tokenPrefix}${token}`; } return config; }, (error) => Promise.reject(error) ); } }; export const CancelDuplicatePlugin = { name: 'CancelDuplicatePlugin', install(instance, options = {}) { const { generateKey = (config) => `${config.method}_${config.url}_${JSON.stringify(config.params)}`, message = '取消重复请求' } = options; const pendingRequests = new Map(); instance.interceptors.request.use( (config) => { const key = generateKey(config); if (pendingRequests.has(key)) { pendingRequests.get(key).cancel(message); } const source = axios.CancelToken.source(); config.cancelToken = source.token; pendingRequests.set(key, source); config._cancelKey = key; return config; }, (error) => Promise.reject(error) ); instance.interceptors.response.use( (response) => { const key = response.config._cancelKey; if (key) pendingRequests.delete(key); return response; }, (error) => { const key = error.config?._cancelKey; if (key) pendingRequests.delete(key); return Promise.reject(error); } ); } }; export const RetryPlugin = { name: 'RetryPlugin', install(instance, options = {}) { const { maxRetries = 3, retryDelay = 1000, retryDelayMultiplier = 2, maxRetryDelay = 30000, retryCondition = (error) => { return !error.response || error.response.status >= 500; } } = options; instance.interceptors.response.use( (response) => response, async (error) => { const config = error.config; if (!config) return Promise.reject(error); config._retryCount = config._retryCount || 0; if (config._retryCount >= maxRetries || !retryCondition(error)) { return Promise.reject(error); } config._retryCount++; const delay = Math.min( retryDelay * Math.pow(retryDelayMultiplier, config._retryCount - 1), maxRetryDelay ); await new Promise(resolve => setTimeout(resolve, delay)); return instance(config); } ); } }; export const LoadingPlugin = { name: 'LoadingPlugin', install(instance, options = {}) { const { showLoading = () => console.log('Loading...'), hideLoading = () => console.log('Loading done'), minDuration = 0 } = options; let requestCount = 0; let minDurationTimer = null; instance.interceptors.request.use( (config) => { if (config.showLoading !== false) { if (requestCount === 0) { showLoading(); } requestCount++; } config._showLoading = config.showLoading !== false; return config; }, (error) => Promise.reject(error) ); const hide = () => { requestCount--; if (requestCount <= 0) { requestCount = 0; if (minDuration > 0) { clearTimeout(minDurationTimer); minDurationTimer = setTimeout(() => { hideLoading(); }, minDuration); } else { hideLoading(); } } }; instance.interceptors.response.use( (response) => { response.config._showLoading && hide(); return response; }, (error) => { error.config?._showLoading && hide(); return Promise.reject(error); } ); } }; export const CachePlugin = { name: 'CachePlugin', install(instance, options = {}) { const { ttl = 5 * 60 * 1000, maxSize = 100, keyGenerator = (config) => `${config.url}_${JSON.stringify(config.params)}` } = options; const cache = new Map(); const accessOrder = []; instance.interceptors.request.use( (config) => { if (config.method !== 'get' || config.cache === false) return config; const key = keyGenerator(config); const cached = cache.get(key); if (cached && Date.now() < cached.expireTime) { const source = axios.CancelToken.source(); config.cancelToken = source.token; setTimeout(() => { source.cancel(JSON.stringify({ __fromCache: true, data: cached.data })); }, 0); } config._cacheKey = key; return config; }, (error) => Promise.reject(error) ); instance.interceptors.response.use( (response) => { const key = response.config._cacheKey; if (key && response.config.method === 'get') { if (cache.size >= maxSize) { const oldest = accessOrder.shift(); cache.delete(oldest); } cache.set(key, { data: response.data, expireTime: Date.now() + ttl }); accessOrder.push(key); } return response; }, (error) => { if (axios.isCancel(error) && error.message) { try { const parsed = JSON.parse(error.message); if (parsed.__fromCache) { return Promise.resolve({ data: parsed.data, config: error.config, status: 200, statusText: 'OK (from cache)', headers: {} }); } } catch {} } return Promise.reject(error); } ); } }; export const ErrorHandlerPlugin = { name: 'ErrorHandlerPlugin', install(instance, options = {}) { const { handler = (error) => console.error('[ErrorHandler]', error.message), errorMap = { 400: '请求参数错误', 401: '未授权,请重新登录', 403: '拒绝访问', 404: '请求的资源不存在', 500: '服务器内部错误', 502: '网关错误', 503: '服务不可用', 504: '网关超时' } } = options; instance.interceptors.response.use( (response) => response, (error) => { if (error.response) { const status = error.response.status; error.message = errorMap[status] || `请求失败: ${status}`; } else if (error.request) { error.message = '网络错误,请检查网络连接'; } handler(error); return Promise.reject(error); } ); } }; // ==================== 预设 ==================== export function createAxiosWithPreset(preset = 'default', config = {}) { const instance = createAxios(config); const presets = { minimal: [LogPlugin], default: [ { plugin: LogPlugin }, { plugin: TokenPlugin }, { plugin: CancelDuplicatePlugin }, { plugin: ErrorHandlerPlugin } ], full: [ { plugin: LogPlugin }, { plugin: TokenPlugin }, { plugin: CancelDuplicatePlugin }, { plugin: RetryPlugin, options: { maxRetries: 3 } }, { plugin: LoadingPlugin }, { plugin: CachePlugin }, { plugin: ErrorHandlerPlugin } ] }; (presets[preset] || presets.default).forEach(({ plugin, options }) => { instance.use(plugin, options); }); return instance; } export default createAxios;
如果对你有用的话,可以打赏哦
打赏
ali pay
wechat pay

本文作者:繁星

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!