React 中基于 Axios 的二次封装(含请求守卫)
下面是一个完整的 Axios 二次封装方案,包含 GET、POST 等方法的封装,并加入了请求守卫(拦截器)功能。
1. 基础封装 (axiosInstance.js)
javascript
import axios from 'axios';
import { message } from 'antd'; // 使用 Ant Design 的 message 作为示例,可替换为其他 UI 库或自定义
// 创建 axios 实例
const axiosInstance = axios.create({
baseURL: process.env.REACT_APP_API_BASE_URL || '/api', // 基础路径
timeout: 10000, // 请求超时时间
withCredentials: true, // 跨域请求时是否需要使用凭证
});
// 请求拦截器
axiosInstance.interceptors.request.use(
(config) => {
// 在发送请求之前做些什么
// 例如:添加 token
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
// 可以在这里添加全局请求参数或修改 config
// config.params = { ...config.params, lang: 'zh-CN' };
return config;
},
(error) => {
// 对请求错误做些什么
console.error('请求拦截器错误:', error);
return Promise.reject(error);
}
);
// 响应拦截器
axiosInstance.interceptors.response.use(
(response) => {
// 对响应数据做点什么
// 假设后端返回的数据结构为 { code: 200, data: {}, message: '' }
const res = response.data;
if (res.code === 200) {
return res.data; // 直接返回数据部分
} else {
// 处理业务错误
message.error(res.message || '请求错误');
return Promise.reject(new Error(res.message || 'Error'));
}
},
(error) => {
// 对响应错误做点什么
console.error('响应拦截器错误:', error);
if (error.response) {
// 根据 HTTP 状态码处理不同错误
switch (error.response.status) {
case 401:
message.error('未授权,请登录');
// 这里可以跳转到登录页
// window.location.href = '/login';
break;
case 403:
message.error('拒绝访问');
break;
case 404:
message.error('请求的资源不存在');
break;
case 500:
message.error('服务器错误');
break;
default:
message.error(error.response.data.message || '请求失败');
}
} else if (error.message.includes('timeout')) {
message.error('请求超时');
} else {
message.error('网络错误');
}
return Promise.reject(error);
}
);
export default axiosInstance;
2. 请求方法封装 (request.js)
javascript
import axiosInstance from './axiosInstance';
/**
* 封装 GET 请求
* @param {string} url 请求地址
* @param {object} params 请求参数
* @param {object} config 额外配置
* @returns Promise
*/
export function get(url, params = {}, config = {}) {
return axiosInstance.get(url, { params, ...config });
}
/**
* 封装 POST 请求
* @param {string} url 请求地址
* @param {object} data 请求体数据
* @param {object} config 额外配置
* @returns Promise
*/
export function post(url, data = {}, config = {}) {
return axiosInstance.post(url, data, config);
}
/**
* 封装 PUT 请求
* @param {string} url 请求地址
* @param {object} data 请求体数据
* @param {object} config 额外配置
* @returns Promise
*/
export function put(url, data = {}, config = {}) {
return axiosInstance.put(url, data, config);
}
/**
* 封装 DELETE 请求
* @param {string} url 请求地址
* @param {object} params 请求参数
* @param {object} config 额外配置
* @returns Promise
*/
export function del(url, params = {}, config = {}) {
return axiosInstance.delete(url, { params, ...config });
}
/**
* 封装上传请求
* @param {string} url 请求地址
* @param {FormData} formData 表单数据
* @param {object} config 额外配置
* @returns Promise
*/
export function upload(url, formData, config = {}) {
return axiosInstance.post(url, formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
...config,
});
}
// 可以继续添加其他封装方法...
3. 使用示例
javascript
import React, { useEffect, useState } from 'react';
import { get, post } from './request';
function UserList() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
useEffect(() => {
const fetchUsers = async () => {
setLoading(true);
try {
const data = await get('/users');
setUsers(data);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
fetchUsers();
}, []);
const handleAddUser = async () => {
try {
const newUser = { name: 'New User', age: 25 };
const data = await post('/users', newUser);
console.log('添加成功:', data);
// 可以刷新列表或做其他操作
} catch (err) {
console.error('添加失败:', err);
}
};
if (loading) return <div>加载中...</div>;
if (error) return <div>错误: {error}</div>;
return (
<div>
<button onClick={handleAddUser}>添加用户</button>
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</div>
);
}
export default UserList;
4. 高级功能扩展
4.1 请求取消功能
javascript
// 在 axiosInstance.js 中添加
const CancelToken = axios.CancelToken;
const sourceMap = new Map(); // 用于存储请求的取消函数
// 修改请求拦截器
axiosInstance.interceptors.request.use(
(config) => {
// 如果已有相同请求在执行,则取消之前的请求
const requestKey = `${config.method}-${config.url}`;
if (sourceMap.has(requestKey)) {
const cancel = sourceMap.get(requestKey);
cancel('取消重复请求');
}
// 创建新的取消令牌
const source = CancelToken.source();
config.cancelToken = source.token;
sourceMap.set(requestKey, source.cancel);
return config;
},
(error) => {
return Promise.reject(error);
}
);
// 修改响应拦截器
axiosInstance.interceptors.response.use(
(response) => {
// 请求成功后从 map 中移除对应的取消函数
const config = response.config;
const requestKey = `${config.method}-${config.url}`;
sourceMap.delete(requestKey);
return response;
},
(error) => {
if (axios.isCancel(error)) {
console.log('请求已取消:', error.message);
}
// 其他错误处理...
return Promise.reject(error);
}
);
// 导出取消请求的方法
export function cancelRequest(method, url) {
const requestKey = `${method}-${url}`;
if (sourceMap.has(requestKey)) {
const cancel = sourceMap.get(requestKey);
cancel('主动取消请求');
sourceMap.delete(requestKey);
}
}
4.2 请求重试机制
javascript
// 在 axiosInstance.js 中添加
const retryCount = 2; // 重试次数
const retryDelay = 1000; // 重试延迟时间(ms)
// 修改请求拦截器或单独封装一个重试函数
async function retryRequest(config, retryNum = retryCount) {
try {
const response = await axiosInstance(config);
return response;
} catch (error) {
if (retryNum <= 0) {
throw error;
}
console.log(`请求失败,重试 ${retryNum} 次...`);
await new Promise(resolve => setTimeout(resolve, retryDelay));
return retryRequest(config, retryNum - 1);
}
}
// 然后在需要的地方使用 retryRequest 代替直接调用 axiosInstance
5. TypeScript 版本
如果需要 TypeScript 版本,可以这样修改:
typescript
// axiosInstance.ts
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, AxiosError } from 'axios';
interface ResponseData<T = any> {
code: number;
data: T;
message: string;
}
const axiosInstance: AxiosInstance = axios.create({
baseURL: process.env.REACT_APP_API_BASE_URL || '/api',
timeout: 10000,
withCredentials: true,
});
axiosInstance.interceptors.request.use(
(config: AxiosRequestConfig) => {
const token = localStorage.getItem('token');
if (token) {
config.headers!.Authorization = `Bearer ${token}`;
}
return config;
},
(error: AxiosError) => {
return Promise.reject(error);
}
);
axiosInstance.interceptors.response.use(
(response: AxiosResponse<ResponseData>) => {
const res = response.data;
if (res.code === 200) {
return res.data;
} else {
return Promise.reject(new Error(res.message || 'Error'));
}
},
(error: AxiosError) => {
// 错误处理...
return Promise.reject(error);
}
);
export default axiosInstance;
// request.ts
import axiosInstance from './axiosInstance';
export function get<T = any>(url: string, params?: any, config?: AxiosRequestConfig): Promise<T> {
return axiosInstance.get(url, { params, ...config });
}
export function post<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> {
return axiosInstance.post(url, data, config);
}
// 其他方法...
这个封装方案提供了:
- 基础请求方法的封装
- 请求和响应拦截器(守卫)
- 错误统一处理
- 可扩展的高级功能(请求取消、重试等)
- TypeScript 支持(可选)
你可以根据实际项目需求进行调整和扩展。