在现代前端开发中,HTTP 请求是与后端服务交互的核心。一个功能全面、高度可扩展的请求模块可以显著提升开发效率和代码的可维护性。本文将从零开始,使用 Axios 封装一个灵活的通用请求模块,涵盖 请求基础、动态参数处理、错误处理、上传文件、请求取消 等功能。
虽然 Fetch
是浏览器内置的原生 API,但它在实际开发中的使用存在一些局限性。以下是 Axios 相较于 Fetch 的主要优势:
功能点 | Axios | Fetch |
---|---|---|
浏览器兼容性 | 支持 IE 和旧版浏览器 | 不支持 IE,需引入 Polyfill |
请求拦截器 | 内置支持,便于统一处理请求逻辑 | 需要手动封装拦截器 |
响应拦截器 | 内置支持,便于统一处理响应逻辑 | 需要手动封装拦截器 |
错误处理 | 自动将 HTTP 错误状态码转为 catch 捕获 |
必须手动检查 response.ok |
自动序列化 JSON | 请求体自动序列化,响应体自动解析为对象 | 需要手动调用 JSON.stringify 和 JSON.parse |
请求取消功能 | 内置 CancelToken 功能 |
原生不支持 |
超时配置 | 内置支持 | 必须通过 AbortController 手动实现 |
文件上传 | 内置支持 FormData |
需手动设置 Content-Type |
从这些对比可以看出,Axios 在开发体验和功能的便利性上更胜一筹,尤其适合需要统一管理请求逻辑、错误处理、以及复杂场景(如文件上传和请求取消)的项目。
在项目中使用 Axios 前,需要先安装它。以下是安装 Axios 的方法:
npm install axios
yarn add axios
我们希望封装的请求模块具备以下功能:
GET
、POST
、PUT
、DELETE
等。params
(查询参数)和 data
(请求体数据)。FormData
上传文件。以下是封装的 Axios 请求模块的完整代码。
创建一个 Axios 实例,并设置基础配置:
import axios from 'axios';
// 根据环境动态设置 baseURL
const baseURL =
process.env.NODE_ENV === 'production'
? 'https://api.production.com'
: 'http://localhost:3000';
// 创建 Axios 实例
const axiosInstance = axios.create({
baseURL, // 基础 API 地址
timeout: 10000, // 超时时间设置(毫秒)
withCredentials: true, // 跨域时携带 cookie
headers: {
'Content-Type': 'application/json', // 默认请求头
},
responseType: 'json', // 默认响应类型
});
export default axiosInstance;
防止重复请求,或在组件卸载时取消未完成的请求:
const CancelToken = axios.CancelToken;
const pendingRequests = new Map(); // 存储未完成的请求
/**
* 添加请求到 pendingRequests 中
*/
const addPendingRequest = (config) => {
const requestKey = `${config.method}:${config.url}:${JSON.stringify(config.params)}:${JSON.stringify(config.data)}`;
if (!pendingRequests.has(requestKey)) {
config.cancelToken = new CancelToken((cancel) => {
pendingRequests.set(requestKey, cancel);
});
}
};
/**
* 移除请求
*/
const removePendingRequest = (config) => {
const requestKey = `${config.method}:${config.url}:${JSON.stringify(config.params)}:${JSON.stringify(config.data)}`;
if (pendingRequests.has(requestKey)) {
const cancel = pendingRequests.get(requestKey);
cancel(`请求已取消:${requestKey}`); // 执行取消操作
pendingRequests.delete(requestKey);
}
};
/**
* 清空所有请求
*/
const clearPendingRequests = () => {
pendingRequests.forEach((cancel) => cancel());
pendingRequests.clear();
};
可以在需要取消请求的场景(如组件卸载时)调用 clearPendingRequests()
。
支持通过 FormData
上传文件
/**
* 上传文件
* @param {string} url 上传文件的接口地址
* @param {Object} files 文件数据对象(支持多个文件)
* @param {Object} [extraData] 额外的表单数据
* @param {Object} [headers={}] 自定义请求头
* @returns {Promise} 返回一个 Promise
*/
const uploadFile = (url, files, extraData = {}, headers = {}) => {
const formData = new FormData();
// 将文件和额外数据添加到 FormData
Object.keys(files).forEach((key) => {
formData.append(key, files[key]);
});
Object.keys(extraData).forEach((key) => {
formData.append(key, extraData[key]);
});
return axiosInstance.post(url, formData, {
headers: {
'Content-Type': 'multipart/form-data',
...headers,
},
});
};
支持动态参数、错误处理、失败重试等功能:
/**
* 通用请求方法封装
* @param {Object} options 请求配置选项
* @param {string} options.url 请求地址
* @param {string} [options.method='GET'] 请求方法
* @param {Object} [options.params={}] 查询字符串参数
* @param {Object} [options.data={}] 请求体数据
* @param {Object} [options.headers={}] 自定义请求头
* @param {boolean} [options.showLoading=false] 是否显示全局 Loading
* @param {number} [options.retries=0] 请求失败时的重试次数
* @param {number} [options.retryDelay=1000] 重试间隔时间(毫秒)
* @param {number} [options.timeout=10000] 请求超时时间(毫秒)
* @returns {Promise} 返回一个 Promise
*/
const request = async ({
url,
method = 'GET',
params = {},
data = {},
headers = {},
showLoading = false,
retries = 0,
retryDelay = 1000,
timeout = 10000,
}) => {
const config = { url, method, params, data, headers, timeout };
const sendRequest = async (retryCount) => {
try {
removePendingRequest(config); // 防止重复请求
addPendingRequest(config); // 添加当前请求
const response = await axiosInstance(config);
return response.data;
} catch (error) {
if (retryCount > 0) {
await new Promise((resolve) => setTimeout(resolve, retryDelay));
return sendRequest(retryCount - 1); // 递归重试
}
throw error;
} finally {
removePendingRequest(config); // 请求完成后移除
}
};
if (showLoading) showGlobalLoading(); // 显示 Loading
try {
return await sendRequest(retries);
} finally {
if (showLoading) hideGlobalLoading(); // 隐藏 Loading
}
};
export { request, uploadFile, clearPendingRequests };
在请求发送前,添加拦截器来实现以下功能:
token
,并添加到请求头中。axiosInstance.interceptors.request.use(
(config) => {
// 动态添加 Token
const token = localStorage.getItem('token');
if (token) {
config.headers['Authorization'] = `Bearer ${token}`;
}
// 开启全局 Loading
if (config.showLoading) {
showGlobalLoading(); // 假设有全局 Loading 的控制函数
}
return config;
},
(error) => {
// 请求错误处理
return Promise.reject(error);
}
);
在响应阶段,统一处理以下情况:
axiosInstance.interceptors.response.use(
(response) => {
// 隐藏全局 Loading
hideGlobalLoading(); // 假设有全局 Loading 的控制函数
// 根据业务状态码处理逻辑
const { code, message, data } = response.data;
if (code === 200) {
// 请求成功
return data;
} else {
// 业务错误
console.error(`业务错误:${message}`);
return Promise.reject(new Error(message));
}
},
(error) => {
// 隐藏全局 Loading
hideGlobalLoading();
// 统一处理 HTTP 错误状态码
if (error.response) {
const { status } = error.response;
switch (status) {
case 401:
console.error('未登录,请重新登录');
// 跳转到登录页面
break;
case 403:
console.error('无权限访问');
break;
case 500:
console.error('服务器错误,请稍后重试');
break;
default:
console.error('请求失败');
}
}
return Promise.reject(error);
}
);
import { get } from './request';
get('/api/users', { page: 1, size: 10 }, true)
.then((data) => console.log('成功:', data))
.catch((error) => console.error('失败:', error));
import { post } from './request';
post('/api/login', { username: 'admin', password: '123456' }, true)
.then((data) => console.log('登录成功:', data))
.catch((error) => console.error('登录失败:', error));
通过对 Axios 请求模块的封装,我们成功实现了一个功能全面、灵活易用的通用请求工具。该工具不仅满足了常见的开发需求,还具备了高扩展性和可维护性。其核心功能包括:
GET
、POST
、PUT
、DELETE
等请求场景。在实际开发中,这个封装模块可以极大地提升代码复用性和团队协作效率,同时减少手动处理请求的复杂度。如果有更多个性化需求,也可以在此基础上进一步扩展,如支持更多的请求级功能(例如请求优先级、队列管理等)。
通过这样的封装,我们不仅解决了开发中的常见痛点,也为后续的功能扩展提供了强大的基础。希望这份封装工具能够帮助您更高效地完成项目开发!