PAYATHON 2026

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 不应引入服务端专用依赖,也不能包含需要保密的服务端凭据。

对于当前组件,可以先拆分一组职责清楚的请求,再确认页面的加载、成功和失败状态仍然正常。即使某个查询只使用一次,只要抽出后能明显改善阅读和维护,也有封装的价值。

备注:内容仅供参考。