React 项目中的请求封装进化:从 Axios 到 React Query 的三层架构
一、传统请求封装方式
一开始我在项目里写请求封装的时候,用的是比较传统的方式,就是只封装了 Axios。比如设置 baseURL、加个 token 拦截器、处理一下错误,然后每个模块自己写 API 调用函数,组件里直接调用这些函数拿数据。
这种方式虽然能跑,但后来慢慢发现几个问题:
- 每次都得手动写 loading 状态、错误提示,很重复;
- 组件里经常会有请求重复发的问题,特别是快速切换页面的时候;
- 页面卸载时请求没取消,可能会出现数据覆盖或者内存泄露;
- 状态是本地管理的,不好复用、也不方便统一处理。
二、引入三层架构优化请求封装
后来我改进了写法,现在是把请求分成了三层结构:
- 最底层是 Axios:统一处理拦截器、token、刷新 token 等问题;
- 中间是 API 层:模块化定义每个请求的函数,方便维护;
- 最上层封装 React Query:使用
useQuery
和useMutation
管理状态、缓存、错误和重试。
三、改进后的优势
这样写完之后,页面组件基本就不再需要手动写 loading 和 error 逻辑了,只用关心数据展示和业务逻辑。请求状态、缓存时间、失败重试这些都可以统一配置。
而且像 token 过期自动刷新、请求自动取消、全局错误提示这些都在 Axios 层统一处理,组件层不用关心细节,维护起来更清晰、扩展性也更强。
我现在写封装,会更注重拆层、职责分离,考虑扩展性和团队协作,不再只关注请求本身,而是请求上下游的整个链路怎么更合理地组织。
四、请求生命周期流程图解
axios + 接口管理 + React Query 流程图如下:
【UI组件发起请求】
│
▼
useQuery()/useMutation()
│
▼
React Query 启动请求生命周期
│
├─▶ ① 判断是否已缓存
│ ├─ 有缓存 → 返回 → 跳过网络请求
│ └─ 无缓存 → 发起请求
│
├─▶ ② 请求自动去重(dedupe)
│
▼
触发 queryFn(调用封装的 Axios API)
│
▼
Axios 发起 HTTP 请求(附带 token、拦截器)
│
▼
请求进行中……
│
├─▶ ③ 可自动取消(组件卸载 / key 变化)
│
▼
响应返回 → 进入 React Query 响应流程
│
├─ 成功:
│ └─ 保存数据 → 缓存 → 状态更新 → UI 渲染
│
└─ 失败(如 401):
│
├─▶ 判断是否 token 过期
│
├─ 是 → 启动刷新 token 流程:
│ ├─ 当前正在刷新?是 → 加入等待队列
│ └─ 否 → 发起 refreshToken 请求
│ ├─ 成功 → 更新 token → 重试原请求
│ └─ 失败 → 清除状态 → 跳转登录
│
└─ 非 token 问题:
├─▶ ④ 自动重试(最多 3 次,支持退避)
└─▶ ⑤ 触发 onError / toast / 全局处理
五、总结:关注请求“链路”的组织,而非请求本身
当我们在组件里触发一个请求,比如用 useQuery
或 useMutation
,React Query 就会接管整个请求生命周期,从缓存判断、请求去重,到自动取消、错误处理、缓存管理、重试机制等,都实现了自动化和可配置。
配合底层的 Axios 处理 token、拦截器、错误提示和 token 刷新等通用逻辑,整个请求链路变得清晰、稳定且具备良好的扩展性。组件层可以完全聚焦于数据展示和业务逻辑,实现职责解耦和代码复用,是前端请求管理向工程化迈进的重要一步。
简单示例代码:gitHub仓库地址:https://github.com/MyBrainstorming/react-query.git