第一步:封装企业级 Axios (request.js)
企业级 Axios 通常需要处理:环境变量、请求拦截(加 Token)、响应拦截(统一错误处理)、超时设置以及取消请求的能力。
javascript
// src/utils/request.js
import axios from 'axios';
import { message } from 'antd'; // 假设你用了 antd,或者用 alert
// 1. 创建实例
const service = axios.create({
baseURL: process.env.REACT_APP_API_BASE_URL || '/api', // 环境变量
timeout: 10000, // 请求超时时间
});
// 2. 请求拦截器 (Request Interceptor)
service.interceptors.request.use(
(config) => {
// 在这里添加 Token 或其他公共参数
const token = localStorage.getItem('token');
if (token) {
config.headers['Authorization'] = `Bearer ${token}`;
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
// 3. 响应拦截器 (Response Interceptor)
service.interceptors.response.use(
(response) => {
// 假设后端返回格式统一为 { code: 200, data: [], msg: '' }
const res = response.data;
if (res.code !== 200) {
message.error(res.msg || '请求失败');
// 特定错误处理,例如 401 跳转登录
if (res.code === 401) {
window.location.href = '/login';
}
return Promise.reject(new Error(res.msg));
}
return res; // 直接返回 data 部分,业务层拿到的就是纯净数据
},
(error) => {
message.error(error.message || '网络异常');
return Promise.reject(error);
}
);
export default service;
第二步:封装通用的请求 Hook (useRequest.js)
这个 Hook 参考了你图片中的写法,但它做了增强:支持手动触发 (manual)、依赖刷新 (refreshDeps) 和参数透传。
javascript
// src/hooks/useRequest.js
import { useState, useEffect, useCallback } from 'react';
/**
* 通用的请求 Hook
* @param {Function} api - 请求函数,必须返回一个 Promise
* @param {Array} defaultParams - 默认参数
* @param {Object} options - 配置项
*/
function useRequest(api, defaultParams = [], options = {}) {
const { manual = false, refreshDeps = [] } = options;
const [data, setData] = useState(null);
const [loading, setLoading] = useState(!manual);
const [error, setError] = useState(null);
// 请求函数
const run = useCallback(
async (...args) => {
setLoading(true);
setError(null);
try {
// 合并参数:默认参数 + 手动传入参数
const finalParams = [...defaultParams, ...args];
const res = await api(...finalParams);
setData(res); // 注意:这里依赖 axios 拦截器返回了 res.data
return res;
} catch (err) {
setError(err);
throw err; // 抛出错误,让组件能捕获
} finally {
setLoading(false);
}
},
[api, ...defaultParams] // 依赖项
);
// 自动执行逻辑
useEffect(() => {
if (!manual) {
run();
}
}, [run, manual, ...refreshDeps]);
return {
data,
loading,
error,
run,
};
}
export default useRequest;
第三步:封装具体的业务 API 函数
这一步是为了把 axios 和具体的 URL 隔离,方便后期维护(比如后端改接口名,只改这里)。
javascript
// src/api/user.js
import request from '@/utils/request';
// 导出请求函数,而不是直接调用
export const getUserList = (params) => {
return request.get('/users', { params });
};
export const deleteUser = (id) => {
return request.delete(`/users/${id}`);
};
第四步:组件调用模板(完全复刻你图片的风格)
现在,在组件中使用它就会非常清爽,完美匹配你的需求。
jsx
// UserPage.jsx
import React from 'react';
import { getUserList } from '@/api/user';
import useRequest from '@/hooks/useRequest';
function UserPage() {
// 🎯 重点:完全类似你图片中的调用方式
// 第一个参数传 api 函数,第二个参数传默认参数,第三个参数传 options
const { data, loading, error, run } = useRequest(getUserList, [], {
// manual: true, // 如果需要手动触发,设为 true
});
// 手动触发示例(如果设置了 manual: true)
const handleRefresh = () => {
run();
};
if (loading) return <div>加载中...</div>;
if (error) return <div>出错了: {error.message}</div>;
return (
<div>
<h1>用户列表</h1>
<button onClick={handleRefresh}>刷新数据</button>
<ul>
{data?.list?.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</div>
);
}
export default UserPage;
总结:架构分层
request.js:纯粹的 HTTP 工具,不管业务逻辑,只负责底层通信。api/*.js:接口契约,定义 URL 和参数结构。hooks/useRequest.js:逻辑复用层,处理 Loading、Error、重试等通用逻辑。Component:纯粹的 UI 层,只需要关心data、loading,调用run即可。
这种结构非常稳定,在大型 React 项目中是标准的工程化实践。