React Query 数据获取逻辑是否应该拆分为独立文件或自定义 Hook?
数据小许
结论
可以拆分。你描述的情况已经值得整理:通常可以把普通请求函数放进独立文件,将 React Query 的查询逻辑封装成自定义 Hook,让组件主要负责展示和交互。
300 到 400 行并不是必须拆分的硬性标准。更值得关注的是,组件是否同时处理请求细节、查询参数、数据转换和页面渲染,以至于修改其中一项时很难找到对应代码。
请求函数和自定义 Hook 分别放什么?
你已经在使用 React Query,可以按下面的职责划分:
| 位置 | 适合放置的逻辑 |
|---|---|
| 请求函数文件 | 请求地址、参数拼接、发送请求、检查响应、解析数据 |
| 自定义 Hook | 调用 useQuery、组织 queryKey、配置 queryFn、管理查询的启用条件等 |
| 页面或容器组件 | 获取交互状态、调用 Hook、展示加载状态、错误和数据 |
请求函数本身通常不需要写成 Hook。只有调用 React Hook 或组合查询行为的那一层,才适合使用 use... 命名。
例如,普通的 getProducts 函数可以供查询逻辑调用,useProducts 则负责把它接入 React Query。
如何逐步拆分?
先搬出纯请求逻辑,再判断是否需要抽取自定义 Hook。目录结构可以参考下面的示例:
features/products/
api.js
useProducts.js
ProductsContainer.jsx
下面用商品列表接口演示请求函数的职责。接口地址和返回数据结构需要替换为项目中的实际定义。
// features/products/api.js
export async function getProducts() {
const response = await fetch('/api/products');
if (!response.ok) {
throw new Error(`Request failed: ${response.status}`);
}
return response.json();
}
然后在现有的 useQuery 配置中,把 getProducts 作为 queryFn 使用。这里需要检查响应状态,因为 fetch 遇到 HTTP 错误状态时不会自动抛出异常。
如果组件中仍然保留了较多查询配置,或者多个组件需要相同的查询行为,再把这部分封装成 useProducts。导入方式和配置写法应沿用项目所用的 React Query 版本。
组件可以通过类似下面的方式使用封装后的 Hook:
const productsQuery = useProducts();
组件继续根据查询结果渲染加载提示、错误提示和商品列表。封装时可以保留调用方需要的查询状态和操作,不必只返回数据。
拆分时需要注意什么?
- 移动代码时,检查
queryKey、请求参数、启用条件和已有配置,保留原有查询行为,避免改变请求时机或缓存对应关系。 - 会改变查询结果的变量应体现在
queryKey中。例如,查询使用分类或页码时,查询键也需要区分这些条件,仅把变量传给请求函数还不够。 - 自定义 Hook 应在组件或其他 Hook 的顶层调用,不能因为搬到单独文件,就放进条件分支或点击事件中执行。
- 按业务拆分请求函数和查询 Hook。商品、用户等不同业务可以各自维护,避免形成巨大的通用请求文件,也不必一次设计出复杂的抽象层。
- 保留 Next.js 的客户端边界。从客户端组件使用的请求模块和 Hook 不应引入服务端专用依赖,也不能包含需要保密的服务端凭据。
对于当前组件,可以先拆分一组职责清楚的请求,再确认页面的加载、成功和失败状态仍然正常。即使某个查询只使用一次,只要抽出后能明显改善阅读和维护,也有封装的价值。
备注:内容仅供参考。
