在 Vue3 项目开发中,Axios 作为最主流的 HTTP 请求库,直接裸用往往会导致代码冗余、异常处理混乱、请求逻辑难以维护。尤其在中大型项目中,一套健壮的 Axios 封装方案,能大幅提升开发效率、降低线上问题发生率。本文结合 2026 年前端工程化最佳实践,从基础封装到企业级特性(拦截器、请求缓存、失败重试、取消重复请求),手把手教你封装 Vue3 专属的 Axios 请求库。
一、封装前的准备:明确核心需求
一个合格的 Axios 封装,需要解决以下核心问题:
- 统一请求基准地址、超时时间、请求头配置;
- 全局请求 / 响应拦截(Token 携带、状态码统一处理、错误提示);
- 取消重复请求(避免短时间内多次触发同一接口);
- 请求缓存(减轻服务端压力,提升页面响应速度);
- 失败自动重试(网络抖动场景下提高请求成功率);
- 类型安全(结合 TypeScript 定义请求 / 响应类型);
- 支持 Vue3 组合式 API,可灵活注入到组件 / Pinia 中。
二、基础封装:搭建核心请求实例
1. 环境准备与依赖安装
确保项目已安装 Axios 和 Vue3,推荐结合 TypeScript 开发:
# 安装依赖
npm install axios --save
npm install @types/axios --save-dev # TS类型声明
2. 目录结构设计
推荐在src/utils/request目录下组织封装代码,结构如下:
src/
├── utils/
│ ├── request/
│ │ ├── index.ts # 核心封装入口
│ │ ├── interceptors.ts # 拦截器封装
│ │ ├── cancelRequest.ts # 取消重复请求
│ │ ├── requestCache.ts # 请求缓存
│ │ └── types.ts # 类型定义
├── env.d.ts # 环境变量类型
└── stores/ # Pinia(可选,存储Token)
3. 核心实例封装(index.ts)
先搭建基础的 Axios 实例,配置全局默认参数:
// src/utils/request/types.ts
import type { AxiosRequestConfig, AxiosResponse, AxiosError } from 'axios';
// 自定义请求配置(扩展Axios原生配置)
export interface RequestConfig extends AxiosRequestConfig {
// 是否取消重复请求
cancelRepeat?: boolean;
// 是否开启缓存
useCache?: boolean;
// 缓存过期时间(秒)
cacheTime?: number;
// 失败重试次数
retryCount?: number;
// 重试间隔(毫秒)
retryDelay?: number;
// 是否隐藏错误提示
hideErrorTip?: boolean;
}
// 通用响应格式(需和后端约定)
export interface ApiResponse<T = any> {
code: number;
msg: string;
data: T;
success: boolean;
}
// 缓存数据类型
export interface CacheData {
data: AxiosResponse;
expireTime: number;
}
// src/utils/request/index.ts
import axios from 'axios';
import { setupInterceptors } from './interceptors';
import { cancelRepeatRequest, removePendingRequest } from './cancelRequest';
import { getCache, setCache, clearCache } from './requestCache';
import type { RequestConfig, ApiResponse, CacheData } from './types';
// 创建Axios实例
const service = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL, // 从环境变量读取基准地址
timeout: 10000, // 默认超时时间
headers: {
'Content-Type': 'application/json;charset=utf-8'
}
});
// 安装拦截器
setupInterceptors(service);
// 核心请求方法封装
const request = <T = any>(config: RequestConfig): Promise<T> => {
const {
cancelRepeat = true,
useCache = false,
cacheTime = 60,
retryCount = 3,
retryDelay = 1000,
...axiosConfig
} = config;
// 1. 处理请求缓存
if (useCache) {
const cacheKey = JSON.stringify({ url: axiosConfig.url, params: axiosConfig.params, data: axiosConfig.data });
const cacheData = getCache<CacheData>(cacheKey);
// 缓存未过期,直接返回缓存数据
if (cacheData && Date.now() < cacheData.expireTime) {
return Promise.resolve(cacheData.data.data as T);
}
}
// 2. 取消重复请求
if (cancelRepeat) {
removePendingRequest(config); // 移除已存在的重复请求
cancelRepeatRequest(config); // 添加当前请求到待取消列表
}
// 3. 发送请求(支持重试)
const sendRequest = (count: number): Promise<T> => {
return new Promise((resolve, reject) => {
service.request<ApiResponse<T>>(axiosConfig)
.then((response) => {
// 缓存请求结果
if (useCache) {
const cacheKey = JSON.stringify({ url: axiosConfig.url, params: axiosConfig.params, data: axiosConfig.data });
setCache(cacheKey, {
data: response,
expireTime: Date.now() + cacheTime * 1000
});
}
// 移除取消请求标记
if (cancelRepeat) {
removePendingRequest(config);
}
resolve(response.data.data);
})
.catch((error: AxiosError) => {
// 移除取消请求标记
if (cancelRepeat) {
removePendingRequest(config);
}
// 失败重试逻辑
if (count > 0 && error.code !== 'ECONNABORTED' && !error.response) {
setTimeout(() => {
sendRequest(count - 1).then(resolve).catch(reject);
}, retryDelay);
return;
}
reject(error);
});
});
};
return sendRequest(retryCount);
};
// 封装常用请求方法
export const get = <T = any>(url: string, config?: RequestConfig): Promise<T> => {
return request<T>({ ...config, method: 'GET', url });
};
export const post = <T = any>(url: string, data?: any, config?: RequestConfig): Promise<T> => {
return request<T>({ ...config, method: 'POST', url, data });
};
export const put = <T = any>(url: string, data?: any, config?: RequestConfig): Promise<T> => {
return request<T>({ ...config, method: 'PUT', url, data });
};
export const del = <T = any>(url: string, config?: RequestConfig): Promise<T> => {
return request<T>({ ...config, method: 'DELETE', url });
};
// 导出实例(便于特殊场景直接使用)
export default service;
三、核心特性封装:拦截器、取消请求、缓存、重试
1. 拦截器封装(interceptors.ts)
拦截器是 Axios 封装的核心,负责统一处理请求头、响应状态、错误提示:
// src/utils/request/interceptors.ts
import { ElMessage } from 'element-plus'; // 假设使用Element Plus做提示
import type { AxiosInstance, AxiosRequestConfig, AxiosResponse, AxiosError } from 'axios';
import { useUserStore } from '@/stores/user'; // Pinia存储Token
import type { RequestConfig, ApiResponse } from './types';
// 请求拦截器
const requestInterceptor = (config: RequestConfig) => {
const userStore = useUserStore();
// 携带Token(根据项目实际存储方式调整)
if (userStore.token) {
config.headers.Authorization = `Bearer ${userStore.token}`;
}
// 自定义请求头扩展
config.headers['X-Request-From'] = 'Vue3-Admin';
return config;
};
// 请求错误拦截
const requestErrorInterceptor = (error: AxiosError) => {
ElMessage.error('请求发送失败,请检查网络');
return Promise.reject(error);
};
// 响应拦截器
const responseInterceptor = (response: AxiosResponse<ApiResponse>) => {
const { data } = response;
// 后端约定的成功码(示例:200为成功)
if (data.code !== 200) {
// 业务错误提示(非200且未隐藏提示)
if (!response.config.hideErrorTip) {
ElMessage.error(data.msg || '请求失败');
}
// 特殊状态码处理(如401未登录、403权限不足)
switch (data.code) {
case 401:
const userStore = useUserStore();
userStore.clearToken();
window.location.href = '/login'; // 跳转到登录页
break;
case 403:
ElMessage.warning('暂无权限操作');
break;
}
return Promise.reject(data);
}
return response;
};
// 响应错误拦截
const responseErrorInterceptor = (error: AxiosError) => {
const config = error.config as RequestConfig;
// 隐藏提示则不弹出
if (config?.hideErrorTip) {
return Promise.reject(error);
}
// 网络错误/超时
if (!error.response) {
ElMessage.error('网络异常,请检查网络连接');
} else {
// HTTP状态码错误
const status = error.response.status;
switch (status) {
case 404:
ElMessage.error('请求接口不存在');
break;
case 500:
ElMessage.error('服务器内部错误,请稍后重试');
break;
default:
ElMessage.error(`请求失败,状态码:${status}`);
}
}
return Promise.reject(error);
};
// 安装拦截器
export const setupInterceptors = (instance: AxiosInstance) => {
// 请求拦截
instance.interceptors.request.use(requestInterceptor, requestErrorInterceptor);
// 响应拦截
instance.interceptors.response.use(responseInterceptor, responseErrorInterceptor);
};
2. 取消重复请求(cancelRequest.ts)
避免短时间内多次触发同一接口,导致服务端压力增大或数据错乱:
// src/utils/request/cancelRequest.ts
import type { AxiosRequestConfig } from 'axios';
import axios from 'axios';
import type { RequestConfig } from './types';
// 存储待取消的请求
const pendingRequests = new Map<string, AbortController>();
// 生成请求唯一标识
const generateRequestKey = (config: RequestConfig): string => {
const { method, url, params, data } = config;
return [method, url, JSON.stringify(params), JSON.stringify(data)].join('-');
};
// 取消重复请求
export const cancelRepeatRequest = (config: RequestConfig) => {
const requestKey = generateRequestKey(config);
// 如果已有相同请求,先取消
if (pendingRequests.has(requestKey)) {
const controller = pendingRequests.get(requestKey);
controller?.abort();
pendingRequests.delete(requestKey);
}
// 创建新的AbortController
const controller = new AbortController();
config.signal = controller.signal;
pendingRequests.set(requestKey, controller);
};
// 移除待取消请求
export const removePendingRequest = (config: RequestConfig) => {
const requestKey = generateRequestKey(config);
pendingRequests.delete(requestKey);
};
// 清空所有待取消请求(如路由切换时)
export const clearAllPendingRequests = () => {
pendingRequests.forEach((controller) => {
controller.abort();
});
pendingRequests.clear();
};
3. 请求缓存(requestCache.ts)
对 GET 请求等幂接口做缓存,减少重复请求:
// src/utils/request/requestCache.ts
import type { CacheData } from './types';
// 缓存容器(可替换为localStorage实现持久化)
const cacheMap = new Map<string, CacheData>();
// 获取缓存
export const getCache = <T = CacheData>(key: string): T | null => {
if (cacheMap.has(key)) {
return cacheMap.get(key) as T;
}
return null;
};
// 设置缓存
export const setCache = (key: string, data: CacheData) => {
cacheMap.set(key, data);
// 自动清理过期缓存
setTimeout(() => {
cacheMap.delete(key);
}, data.expireTime - Date.now());
};
// 清除指定缓存
export const clearCache = (key: string) => {
cacheMap.delete(key);
};
// 清空所有缓存
export const clearAllCache = () => {
cacheMap.clear();
};
四、Vue3 中实战使用
1. 接口统一管理
推荐在src/api目录下按模块管理接口,保证代码可维护性:
// src/api/user.ts
import { get, post } from '@/utils/request';
// 用户登录
export const login = (data: { username: string; password: string }) => {
return post<{ token: string; userId: number }>('/user/login', data, {
cancelRepeat: true,
hideErrorTip: false
});
};
// 获取用户信息(开启缓存,缓存5分钟)
export const getUserInfo = (userId: number) => {
return get<{ name: string; avatar: string; roles: string[] }>(`/user/info/${userId}`, {
useCache: true,
cacheTime: 300
});
};
2. 组件中使用(组合式 API)
<template>
<div class="user-page">
<el-button @click="getUserInfo">获取用户信息</el-button>
<div v-if="userInfo">用户名:{{ userInfo.name }}</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { getUserInfo } from '@/api/user';
import { ElMessage } from 'element-plus';
const userInfo = ref<any>(null);
// 获取用户信息
const getUserInfo = async () => {
try {
const res = await getUserInfo(1001);
userInfo.value = res;
} catch (error) {
ElMessage.error('获取用户信息失败');
console.error(error);
}
};
</script>
3. 路由切换时清空请求
在路由守卫中清空待取消的请求和缓存,避免内存泄漏:
// src/router/index.ts
import { createRouter, createWebHistory } from 'vue-router';
import { clearAllPendingRequests } from '@/utils/request/cancelRequest';
import { clearAllCache } from '@/utils/request/requestCache';
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [/* 路由配置 */]
});
// 路由切换时清空请求和缓存
router.beforeEach((to, from, next) => {
clearAllPendingRequests();
clearAllCache();
next();
});
export default router;
五、企业级优化建议
- 环境隔离:通过 Vite 环境变量区分开发 / 测试 / 生产环境的接口地址,避免手动修改;
- 请求日志:对接前端监控平台(如 Sentry),记录失败请求的详细信息(URL、参数、状态码),便于线上问题排查;
- 限流控制:对高频接口(如搜索)添加请求限流,避免短时间内触发过多请求;
- TypeScript 强化:为所有接口定义严格的请求 / 响应类型,避免 any 类型滥用;
- Mock 数据集成:结合 Vite-plugin-mock 实现本地 Mock,脱离后端联调也能开发;
- 性能监控:统计接口响应时间,对慢接口(如响应 > 2s)做告警或优化。
六、总结
一套完善的 Axios 封装,是 Vue3 项目工程化的重要组成部分。本文从基础实例搭建到企业级特性封装,覆盖了请求拦截、取消重复请求、缓存、重试等核心场景,既保证了代码的复用性和可维护性,又能解决实际开发中的各类痛点。
在 2026 年的前端开发中,Axios 封装不再只是 “能发请求就行”,而是要结合工程化、类型安全、用户体验等维度综合考量。希望这套封装方案能帮你在项目中少走弯路,打造更健壮的请求层体系。
最后,封装方案需根据团队实际业务调整(如 UI 组件库、状态管理库的差异),核心思路是 “统一配置、统一处理、灵活扩展”,让请求逻辑成为项目的 “基础设施”,而非重复造轮子的负担。