Axios 封装
在现代前端开发中,HTTP 请求是与后端服务交互的核心方式。本项目对 Axios 进行了深度封装,提供了统一的请求管理机制,以提高代码的可维护性和开发效率。
为什么封装 Axios
直接使用 Axios 虽然已经很方便,但在大型项目中,我们通常需要:
- 统一的请求/响应处理:对请求头、参数、响应数据进行统一处理
- 错误处理:统一处理网络错误、业务错误等
- 拦截器管理:添加请求和响应拦截器,处理通用逻辑
- 配置管理:统一管理请求配置,如超时时间、基础URL等
- 重试机制:在网络不稳定时自动重试请求
- 取消重复请求:避免重复发送相同请求
- Token 管理:自动携带认证信息
封装结构
项目中的 Axios 封装主要位于 [src/utils/http/](file:///Users/wuxingxi/Desktop/vite-vue3-h5-template/apps/h5-template/src/utils/http) 目录下:
src/utils/http/
├── index.ts # Axios 实例创建和核心封装
├── httpEnum.ts # 枚举类型定义
└── types.ts # 类型定义核心封装 (index.ts)
项目基于 [@miracle-web/utils](file:///Users/wuxingxi/Desktop/vite-vue3-h5-template/apps/h5-template/package.json#L35-L35) 中的 [MAxios](file:///Users/wuxingxi/Desktop/vite-vue3-h5-template/node_modules/.pnpm/@miracle-web+utils@0.0.6/node_modules/@miracle-web/utils/es/axios/index.d.mts#L54-L54) 类进行了二次封装,主要包含以下几个部分:
请求转换器 (transform)
定义了请求各个阶段的处理逻辑:
const transform: AxiosTransform = {
/**
* @description: 请求之前处理config
*/
beforeRequestHook: (config, options) => {
// 处理URL前缀、参数、时间戳等
},
/**
* @description: 请求成功数据处理
*/
requestSuccessResult: (response: AxiosResponse<Result>, options: RequestOptions) => {
// 处理响应数据,显示消息提示等
},
/**
* @description: 请求拦截器处理
*/
requestInterceptors: (config: InternalAxiosRequestConfig, options: CreateAxiosOptions) => {
// 添加token、平台信息等
},
/**
* @description: 响应拦截器处理
*/
responseInterceptors: (response: AxiosResponse) => {
// 处理业务错误、token过期等
},
/**
* @description: 响应错误处理
*/
responseInterceptorsCatch: error => {
// 处理网络错误、取消请求等
},
};Axios 实例创建
通过 createAxios 函数创建 Axios 实例:
function createAxios(opt?: Partial<CreateAxiosOptions>) {
return new MAxios(
deepMerge(
{
timeout: 10 * 1000,
// token 前缀 Bearer
authenticationScheme: "",
// 接口前缀
prefixUrl: urlPrefix,
// 如果是json格式
headers: { "Content-Type": ContentTypeEnum.JSON },
// 数据处理方式
transform,
// 配置项,下面的选项都可以在独立的接口请求中覆盖
requestOptions: {
// 默认将prefix 添加到url
joinPrefix: true,
// 是否返回原生响应头
isReturnNativeResponse: false,
// 需要对返回数据进行处理
isTransformResponse: true,
// post请求的时候添加参数到url
joinParamsToUrl: false,
// 格式化提交参数时间
formatDate: true,
// 接口地址
apiUrl: getEnvConfig().apiUrl,
// 接口拼接地址
urlPrefix,
// 是否加入时间戳
joinTime: true,
// 忽略重复请求
ignoreCancelToken: true,
// 是否携带token
withToken: true,
retryOptions: {
isRetry: true,
retryCount: 2,
__retryCount: 0,
retryWaitTime: 1000,
},
},
},
opt || {}
)
);
}
export const http = createAxios();枚举定义 (httpEnum.ts)
定义了常用的枚举类型:
/**
* @description: 请求结果集
*/
export enum ResultEnum {
SUCCESS = 200,
TOKEN_EXPIRED = 401,
ERROR = 300,
TIMEOUT = 10042,
}
/**
* @description: 请求方法
*/
export enum RequestEnum {
GET = "GET",
POST = "POST",
PATCH = "PATCH",
PUT = "PUT",
DELETE = "DELETE",
}
/**
* @description: 常用的contentTyp类型
*/
export enum ContentTypeEnum {
// json
JSON = "application/json;charset=UTF-8",
// text
TEXT = "text/plain;charset=UTF-8",
// form-data 一般配合qs
FORM_URLENCODED = "application/x-www-form-urlencoded;charset=UTF-8",
// form-data 上传
FORM_DATA = "multipart/form-data;charset=UTF-8",
}使用方式
API 接口定义
在 [src/api/](file:///Users/wuxingxi/Desktop/vite-vue3-h5-template/apps/h5-template/src/api) 目录下按模块定义 API 接口:
// src/api/system/user.ts
import { http } from "@/utils/http";
export interface BasicResponseModel<T = any> {
code: number;
message: string;
data: T;
}
/**
* @description: 用户登录
*/
export function login(params: any) {
return http.request<BasicResponseModel>(
{
url: "/login",
method: "POST",
params,
},
{
successMessageText: "登录成功,即将进入系统",
}
);
}
/**
* @description: 获取用户信息
*/
export function getUserInfo() {
return http.request({
url: "/getUserInfo",
method: "get",
});
}在组件中使用
<script setup lang="ts">
import { login, getUserInfo } from "@/api/system/user";
import { useUserStore } from "@/store/modules/user";
const userStore = useUserStore();
const handleLogin = async () => {
try {
const res = await login({ username: "admin", password: "123456" });
if (res) {
userStore.setToken(res.data.token);
// 跳转到首页
}
} catch (error) {
console.error("登录失败", error);
}
};
const fetchUserInfo = async () => {
try {
const res = await getUserInfo();
userStore.setUserInfo(res.data);
} catch (error) {
console.error("获取用户信息失败", error);
}
};
</script>核心特性
1. 自动携带 Token
在请求拦截器中自动从 Pinia store 获取并携带 Token:
requestInterceptors: (config: InternalAxiosRequestConfig, options: CreateAxiosOptions) => {
const userStore = useUserStoreWidthOut();
const token = userStore.getToken;
// jwt token
if (token && config.requestOptions?.withToken) {
config.headers.Authorization = options.authenticationScheme
? `${options.authenticationScheme} ${token}`
: token;
}
// ...
};2. 智能错误处理
根据不同的错误码进行相应的处理:
responseInterceptors: (response: AxiosResponse) => {
const { code, message } = response.data;
if (code === ResultEnum.ERROR) {
showFailToast(message);
}
if (code === ResultEnum.TOKEN_EXPIRED) {
showDialog({
title: "提示",
message: "登录身份已失效,请重新登录!",
}).then(() => {
window.location.href = PageEnum.BASE_LOGIN;
});
}
return response;
};3. 请求重试机制
在网络不稳定时自动重试请求:
retryOptions: {
isRetry: true,
retryCount: 2,
__retryCount: 0,
retryWaitTime: 1000
}4. 取消重复请求
通过 ignoreCancelToken 配置避免重复请求:
// 忽略重复请求
ignoreCancelToken: true,5. 环境配置支持
通过环境变量配置不同的 API 地址:
// 接口地址
apiUrl: getEnvConfig().apiUrl,最佳实践
- 模块化管理:按业务模块组织 API 接口
- 类型安全:为请求参数和响应数据定义 TypeScript 接口
- 统一错误处理:通过拦截器统一处理错误,避免在业务代码中重复处理
- 合理配置:根据不同接口需求合理配置请求选项
- 避免硬编码:使用枚举定义状态码、请求方法等常量
通过这套封装,项目实现了统一、高效、可维护的 HTTP 请求管理,大大提升了开发效率和代码质量。