news 2026/9/11 14:33:08

深入掌握 Refine 的 useMany Hook:批量数据获取、实时订阅与进阶用法全解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
深入掌握 Refine 的 useMany Hook:批量数据获取、实时订阅与进阶用法全解

深入掌握 Refine 的 useMany Hook:批量数据获取、实时订阅与进阶用法全解

【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards & B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine

导读

useMany是 Refine 中用于批量获取多条记录的核心数据 Hook。本文以 useMany 官方文档 为骨架,结合仓库源码 useMany.ts 与测试用例 useMany.spec.tsx,系统讲解其工作原理、全部属性(resource、ids、queryOptions、meta、通知、实时订阅、加载超时等)、返回值与最佳实践。读完本文,你将掌握如何在 Refine 应用中高效调用getMany批量拉取数据、处理降级回退、接入实时更新,并能在自定义数据提供器中正确实现与消费getMany方法。

一、useMany 是什么:基于 TanStack Query 的批量查询封装

useMany是 Refine 对 TanStack Query 的useQuery的扩展版本,支持useQuery的全部特性并增加了 Refine 专属能力。它的定位是一次请求获取多条记录,与一次只取一条的useOne、分页列表的useList形成互补。

其核心机制在源码 useMany.ts 中清晰可见:

  • 它使用从<Refine>传入的dataProvidergetMany方法作为查询函数(query function)
  • 它基于提供的属性生成查询键(query key)用于缓存数据,你可以在 TanStack Query Devtools 中直接查看该查询键。

从源码可见,查询键由keys().data(...).resource(...).action("many").ids(...).params(...)链式构建,这意味着idsresourcemeta发生变化时,查询键随之变化,useMany会自动触发新的请求——这正是"属性变化即重新拉取"的底层原理(见 useMany.ts)。

没有 getMany 时的降级策略

一个值得特别注意的设计是:如果数据提供器没有实现getMany方法,useMany会退而使用getOne方法,为每个 id 逐个发起请求。源码中的实现逻辑如下:

if (getMany) { return getMany({ resource: resource?.name || "", ids, meta }); } return handleMultiple( ids.map((id) => getOne<TQueryFnData>({ resource: resource?.name || "", id, meta }), ), );

见 useMany.ts。文档明确指出这种做法不推荐,因为它会为每个 id 产生一次网络请求(N 次请求),效率远低于单次getMany。因此,当你的应用需要批量获取数据时,最好在数据提供器中实现真正的getMany方法。

二、基础用法:一个可交互的完整示例

useMany的基础用法非常简单,只需提供resourceids两个属性。下面是文档自带的实时预览示例(源文件见 _basic-usage-live-preview.md):

import { useState } from "react"; import { useMany, HttpError } from "@refinedev/core"; interface IProduct { id: number; name: string; material: string; } const ProductList: React.FC = () => { const [ids, setIds] = useState([1, 2, 3]); const { result, query: { isLoading, isError }, } = useMany<IProduct, HttpError>({ resource: "products", ids, }); const products = result?.data ?? []; if (isLoading) { return <div>Loading...</div>; } if (isError) { return <div>Something went wrong!</div>; } return ( <div> {products.map((product) => ( <ul key={product.id}> <li key={product.id}> {product.id} - {product.name}{" "} <button onClick={() => setIds((prev) => prev.filter((id) => id !== product.id)) } > remove </button> </li> </ul> ))} <button onClick={() => { setIds((prev) => [...prev, Math.floor(Math.random() * 150) + 1]); }} > Add new product </button> </div> ); };

这个示例演示了三个关键行为:

  1. 初始批量获取:挂载时携带ids: [1, 2, 3]发起一次getMany请求,一次性渲染三条产品记录;
  2. 动态移除:点击某条记录的 "remove" 按钮会从ids数组中过滤掉对应 id,useMany检测到ids变化后自动重新请求,列表随之收缩;
  3. 动态新增:点击 "Add new product" 会向ids追加一个随机 id,触发新的请求并拉取该 id 对应的记录。

同时注意useMany的返回结构:query对象是 TanStack Query 的查询结果(包含isLoadingisErrordata等标准字段),而result.data是被展开为数组的记录列表。测试用例 useMany.spec.tsx 也验证了result.data一定是一个数组(Array.isArray(manyResult.data)为真)。

三、完整属性(Properties)详解

resource(必填)

resource会作为参数传递给数据提供器的getMany方法。它通常是对应的 API 端点路径,但具体如何解析完全取决于你在getMany方法中的实现:

useMany({ resource: "categories", });

关于如何自定义getMany方法,参见 创建数据提供器教程。

当存在多个同名资源时,可以传入identifier代替资源的name。注意:identifier仅作为资源的主匹配键,数据提供器方法内部仍使用在<Refine>组件中定义的name。详见 Refine 组件的 identifier 说明。

ids(必填)

ids用于指定要获取哪些记录,同样会传递给getMany方法:

useMany({ ids: [1, 2, 3], });

在源码中,ids的类型为BaseKey[](见 useMany.ts),BaseKey通常是string | number。请求是否执行取决于enabled: hasIds && hasResource(见 useMany.ts),即只有当ids是数组且resource存在时,请求才会真正发起。如果缺少idsresource,源码会通过warnOnce打印出带链接的警告信息,帮助开发者快速定位问题(见 useMany.ts 与 useMany.ts)。

dataProviderName

当应用配置了多个数据提供器时,用该属性指定使用哪一个:

useMany({ dataProviderName: "second-data-provider", });

源码默认值为"default"(见 useMany.ts)。内部通过pickDataProvider(identifier, dataProviderName, resources)解析出最终要使用的数据提供器,并将它写入meta.dataProviderName一并传给getMany(见 [useMany.ts](https://link.gitcode.com/i/e42bc478df3747128178a94e655ef672#L135-L139, L170-L173)),因此你的getMany实现也能感知到当前使用的是哪个数据提供器。

queryOptions

queryOptions用于向底层useQuery传递额外的配置选项:

useMany({ queryOptions: { retry: 3, enabled: false, }, });

从源码看,queryOptions的类型是MakeOptional<UseQueryOptions<...>, "queryKey" | "queryFn">(见 useMany.ts),即queryKeyqueryFn由 Refine 内部接管,开发者不能覆盖,其余 TanStack Query 选项均可透传。若你手动把enabled设为true,即使缺少idsresource,请求也会被强制触发(源码中的manuallyEnabled分支就是为这种场景设计的)。

更多选项参考 TanStack Query 的 useQuery 文档。

meta

meta是一个特殊属性,用于向数据提供器方法传递额外信息,典型用途包括:

  • 针对特定场景定制数据提供器行为;
  • 使用普通 JavaScript 对象(JSON)生成 GraphQL 查询。

下面的示例通过metagetMany方法传递自定义请求头:

import { stringify } from "query-string"; useMany({ // highlight-start meta: { headers: { "x-meta-data": "true" }, }, // highlight-end }); const myDataProvider = { //... getMany: async ({ resource, ids, // highlight-next-line meta, }) => { // highlight-next-line const headers = meta?.headers ?? {}; const url = `${apiUrl}/${resource}?${stringify({ id: ids })}`; //... //... // highlight-next-line const { data } = await httpClient.get(`${url}`, { headers }); return { data, }; }, //... };

在源码层面,meta会经过useMeta()与资源级元数据合并得到combinedMeta(见 useMany.ts),并在调用getMany时连同查询上下文(prepareQueryContext,其中包含分页、排序、过滤等查询参数)一起传入:

const meta = { ...combinedMeta, ...prepareQueryContext(context as any), };

见 useMany.ts。测试用例 useMany.spec.tsx 专门验证了 hook 参数中的meta会被完整透传给数据提供器。

更多说明参见 General Concepts 文档的 meta 概念。

successNotification / errorNotification

这两个属性用于自定义成功与失败时的通知,需要配合NotificationProvider使用:

useMany({ successNotification: (data, ids, resource) => { return { message: `${data.title} Successfully fetched.`, description: "Success with no errors", type: "success", }; }, }); useMany({ errorNotification: (data, ids, resource) => { return { message: `Something went wrong when getting ${data.id}`, description: "Error", type: "error", }; }, });

对应实现细节:

  • 成功通知:请求成功后,若successNotification是函数则以(queryResponse.data, ids, identifier)为参数调用,再交给handleNotification(见 useMany.ts);
  • 失败通知:请求失败时先调用checkError(error)触发鉴权错误处理(如登出跳转),再发送错误通知;默认消息格式为`Error (status code: ${statusCode})`,并带有${ids[0]}-${identifier}-getMany-notification这样的去重键,防止同一请求反复弹错(见 useMany.ts)。

liveMode / onLiveEvent / liveParams(实时更新)

这三个属性依赖LiveProvideruseMany挂载时会调用liveProvidersubscribe方法订阅指定频道,从而实现数据实时刷新。

  • liveMode:决定收到相关实时事件时是否自动更新数据,取值为"auto"(自动)或"manual"(手动):
useMany({ liveMode: "auto", });
  • onLiveEvent:订阅到达新事件时执行的回调:
useMany({ onLiveEvent: (event) => { console.log(event); }, });
  • liveParams:透传给liveProvidersubscribe方法的额外参数。

源码中的订阅逻辑位于 useMany.ts:订阅频道为resources/${resource?.name ?? ""},订阅类型为"*",并在params中携带idsmetasubscriptionType: "useMany"以及你传入的liveParams

实时能力仅在配置了 Live Provider 时可用。

overtimeOptions(加载超时检测)

当你希望请求耗时过长时展示加载提示,可以使用overtimeOptionsinterval是检测间隔(毫秒),onInterval是每个间隔触发的回调:

const { overtime } = useMany({ //... overtimeOptions: { interval: 1000, onInterval(elapsedInterval) { console.log(elapsedInterval); }, }, }); console.log(overtime.elapsedTime); // undefined, 1000, 2000, 3000 4000, ... // 使用示例:超过 4 秒就给出提示 { elapsedTime >= 4000 && <div>this takes a bit longer than expected</div>; }

源码中该能力由useLoadingOvertimeHook 提供,其内部以queryResponse.isFetching作为"正在加载"的判定依据,请求完成后elapsedTime恢复为undefined(见 useMany.ts)。

四、返回值(Return Values)

useMany返回 TanStack QueryuseQuery的全部返回值,并额外提供resultovertime

const { query, result, overtime } = useMany();
  • query:类型为QueryObserverResult<{ data: TData[]; error: TError }>,包含isLoadingisErrorisSuccessdataerror等标准字段,可直接与 TanStack Query 生态(如 Devtools、isFetching)配合;
  • result:Refine 提供的便捷结构,result.dataTData[]数组。源码中当查询未完成时它返回Object.freeze([])冻结的空数组,保证类型安全与不变性(见 [useMany.ts](https://link.gitcode.com/i/e42bc478df3747128178a94e655ef672#L88, L261-L267));
  • overtime{ elapsedTime?: number },配合上面的overtimeOptions使用。

五、TypeScript 泛型参数

useMany支持三个泛型参数,用于获得完整的类型推导:

参数说明类型默认值
TQueryFnData查询函数返回的结果数据类型,继承自BaseRecordBaseRecordBaseRecord
TError自定义错误对象,继承自HttpErrorHttpErrorHttpError
TDataselect函数返回的数据类型,继承自BaseRecord;未指定时默认为TQueryFnDataBaseRecordTQueryFnData

典型用法:

useMany<IProduct, HttpError>({ resource: "products", ids, });

六、源码视角:useMany 的完整执行链路

综合 useMany.ts 的完整实现,一次useMany调用的执行链路可以归纳为以下几步:

  1. 资源解析useResourceParams根据传入的resource解析出最终资源名与identifier
  2. 数据提供器选择useDataProvider+pickDataProvider确定使用哪个数据提供器,同时解析出getManygetOne两个方法;
  3. 实时订阅注册useResourceSubscriptionresources/${resource}为频道发起订阅(需 Live Provider);
  4. 发起查询useQuery使用链式构建的查询键,queryFn优先调用getMany,缺失时回退为逐 id 调用getOne;请求仅在ids有效且resource存在时启用;
  5. 通知与错误处理:成功后触发successNotification,失败后先走useOnError(鉴权错误处理)再触发errorNotification
  6. 超时检测useLoadingOvertime监听isFetching输出overtime.elapsedTime

这条链路在 useMany.spec.tsx 的测试中被逐一验证,包括与 REST JSON 服务器集成(返回两条数据)、result.data的数组形态、meta的透传等场景,可作为你理解行为与编写自定义数据提供器时的参照。

七、最佳实践小结

  • 优先实现getMany:只要业务中存在批量取数,就在数据提供器中实现getMany,避免useMany降级为 N 次getOne请求;
  • 善用ids的响应式:把ids放进useState(如本文示例),增删 id 即可驱动自动重新请求,无需手动调用刷新;
  • queryOptions.enabled控制时机:如页面尚未拿到 id 列表时,用enabled: false阻止请求,待数据就绪再置为true
  • meta透传业务上下文:请求头、GraphQL 片段等与单次请求绑定的信息放入meta,保持数据提供器方法签名稳定;
  • 实时场景打开liveMode: "auto":配合 Live Provider 让表格/详情页在多端协同编辑场景下自动更新;
  • 超时提示用overtimeOptions:以interval细粒度控制"加载过久"的提示时机,提升弱网环境下的用户体验。

【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards & B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/11 14:32:29

SpringBoot冷链生鲜系统:温度监控与智能库存管理实践

1. 项目背景与核心价值冷链运输生鲜销售系统是当前生鲜电商和物流行业的核心基础设施之一。随着消费者对生鲜产品质量要求的不断提高&#xff0c;传统的常温运输方式已经无法满足高品质生鲜产品的配送需求。根据行业数据显示&#xff0c;2022年我国冷链物流市场规模已突破4000亿…

作者头像 李华
网站建设 2026/9/11 14:31:46

Paperxie 五大核心板块功能详解|从开题到答辩,关键环节全覆盖

很多同学用 Paperxie&#xff0c;只知道能写作和查重&#xff0c;却忽略了其他同样强大的功能板块。Paperxie 十大核心板块覆盖从开题到答辩的全流程&#xff0c;每个板块都精准对应毕设的一个关键环节&#xff0c;用好这些功能&#xff0c;论文效率直接翻倍。 今天随机挑选 5…

作者头像 李华
网站建设 2026/9/11 14:31:22

工业价格预测实战:避开数据口径陷阱与模型选型误区

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/11 14:29:34

DMA原理与实战:从嵌入式到SoC的硬件协处理器详解

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/11 14:26:10

AutoDL回收站清理:彻底释放GPU云服务器磁盘空间

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华