
Refine v5 数据获取核心useMany Hook 完整指南【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refineuseMany是 Refine v5 中用于一次性批量获取多条记录的核心数据 Hook它在 TanStack Query 的useQuery之上扩展而来以 data provider 的getMany方法作为查询函数并提供实时订阅、通知、加载超时检测等增强能力。通过本文你将掌握useMany的完整属性体系、内部实现原理、回退机制与最佳实践能够用它对任意自定义 API 构建高效、可扩展的批量数据流。本文以 documentation/docs/data/hooks/use-many/index.md 为骨架结合 packages/core/src/hooks/data/useMany.ts 源码与 packages/core/src/hooks/data/useMany.spec.tsx 测试用例展开。useMany 是什么useMany是 TanStack Query 的useQuery的扩展版本它支持useQuery的全部特性并额外增加了 Refine 特有的能力它使用传入Refine组件的data provider 的getMany方法作为查询函数query function它使用一个**查询键query key**来缓存数据该查询键由传入的属性生成你可以通过 TanStack Query Devtools 查看生成的查询键它适用于需要从 API 批量获取多条记录的场景返回数据以及一组控制查询的函数。没有 getMany 时的回退机制如果你的 data provider 没有实现getMany方法useMany会自动回退到getOne方法对传入的每个 id 逐个发起请求。文档明确指出这种做法不推荐因为它会为每个 id 产生一次网络请求。更好的做法是在 data provider 中实现getMany方法用一次请求批量返回数据。从源码结构看这一回退逻辑位于 packages/core/src/hooks/data/useMany.tsconst { getMany, getOne } dataProvider(pickedDataProvider); // ... queryFn: (context) { const meta { ...combinedMeta, ...prepareQueryContext(context), }; if (getMany) { return getMany({ resource: resource?.name || , ids, meta, }); } return handleMultiple( ids.map((id) getOneTQueryFnData({ resource: resource?.name || , id, meta, }), ), ); };而在 packages/core/src/contexts/data/types.ts 中getMany在DataProvider接口里被声明为可选方法getMany?: ...这正好解释了为什么需要上述回退逻辑——并非所有 data provider 都实现了它。基本用法useMany期望一个resource属性和一个ids属性它们会作为参数传递给 data provider 的getMany方法。当这些属性发生变化时useMany会触发一次新的请求。文档中的基本用法示例来自 documentation/docs/data/hooks/use-many/_basic-usage-live-preview.md展示了一个完整的可运行场景通过ids状态批量加载产品列表并支持从列表中移除产品或随机添加新产品从而演示ids变化时自动重新请求的机制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 }, } useManyIProduct, HttpError({ resource: products, ids, }); const products result?.data ?? []; if (isLoading) { return divLoading.../div; } if (isError) { return divSomething 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 ); };注意这里useMany的返回值结构解构出result其.data为记录数组和queryisLoading、isError等 TanStack Query 状态字段。这与useQuery直接返回查询结果不同是 Refine 数据 Hook 的统一返回约定。底层查询键的构成useMany会为每次查询生成一个结构化的查询键用于缓存。从 packages/core/src/hooks/data/useMany.ts 可以看到查询键的构建链queryKey: keys() .data(pickedDataProvider) .resource(identifier) .action(many) .ids(...(ids ?? [])) .params({ ...(preferredMeta || {}), }) .get(),即查询键由data provider 名称 → resource 标识identifier→ 动作类型 many → ids 数组 → meta 参数组合而成。测试用例 packages/core/src/hooks/data/useMany.spec.tsx 验证了当使用identifier时查询键中的 resource 段使用identifier而非name// 当 resource 传入 featured-postsidentifier时生成的查询键为 [data, default, featured-posts, many, [1, 2], expect.any(Object)]这意味着相同的 data provider、identifier、ids 与 meta 组合会命中同一缓存这是 TanStack Query 缓存去重的基础。实时更新Realtime Updates此功能仅在使用了 Live Provider 时可用。当useMany挂载时它会调用 live provider 的subscribe方法传入channel、resource等参数。这在你需要订阅实时更新时非常有用。源码中的订阅逻辑位于 packages/core/src/hooks/data/useMany.ts通过useResourceSubscription实现订阅通道为resources/${resource?.name}订阅类型为useManyuseResourceSubscription({ resource: identifier, types: [*], params: { ids: ids ?? [], meta: combinedMeta, subscriptionType: useMany, ...liveParams, }, channel: resources/${resource?.name ?? }, enabled: isEnabled, liveMode, onLiveEvent, meta: { ...meta, dataProviderName: pickedDataProvider, }, });测试用例 packages/core/src/hooks/data/useMany.spec.tsx 对实时订阅行为做了详细验证订阅时传入的channel为resources/postsparams中包含ids、subscriptionType: useMany、resource与meta当全局liveMode为off时不会调用subscribe但当全局liveMode为off而 Hook 内显式传入liveMode: auto时仍会订阅组件卸载时会调用unsubscribe取消订阅若queryOptions.enabled为false则不会发起订阅。Properties属性详解resource必填该参数会作为参数传递给 data provider 的getMany方法。它通常被用作 API 端点路径但具体如何解释取决于你在getMany方法中如何处理resource。useMany({ resource: categories, });关于如何实现 data provider请参考 创建 data provider 教程。多个同名资源时使用 identifier如果你有多个同名的资源可以传入identifier而不是资源的name。identifier仅用作资源的主要匹配键data provider 的方法仍然使用Refine/组件中定义的资源name来工作。更多信息参见Refine/组件的identifier文档。在 documentation/docs/core/refine-component/index.md 中对此有明确说明identifier值作为资源的主要匹配键允许你在多个共享相同name的资源之间进行区分——例如一个posts资源使用默认 data provider另一个posts资源使用 typicode data provider此时可用identifier区分它们。测试用例 packages/core/src/hooks/data/useMany.spec.tsx 验证了identifier的三个行为正确选择 data provider、用identifier生成查询键、以及获取对应资源的meta。ids必填该属性会作为参数传递给 data provider 的getMany方法用于确定要获取哪些记录。其类型为BaseKey[]其中BaseKey是string | number见 interface-references。useMany({ ids: [1, 2, 3], });缺失ids或resource时的警告源码在 packages/core/src/hooks/data/useMany.ts 中使用warnOnce对缺失属性发出警告warnOnce( !hasIds !manuallyEnabled, idsWarningMessage(ids, resource?.name || resource?.identifier || ), ); warnOnce(!hasResource !manuallyEnabled, resourceWarningMessage());即当ids或resource缺失且未手动设置queryOptions.enabled: true时会在控制台打印警告信息[useMany]: Missing ids prop.或[useMany]: Missing resource prop.且查询不会执行。对应测试见 packages/core/src/hooks/data/useMany.spec.tsx缺失属性时fetchStatus为idle且getMany不会被调用而手动enabled: true时不会警告且会发起请求。dataProviderName当你有多个 data provider 时该属性允许你指定使用哪一个useMany({ dataProviderName: second-data-provider, });在源码中data provider 的选择通过pickDataProvider(identifier, dataProviderName, resources)完成useMany.ts。测试 useMany.spec.tsx 验证了当资源在meta.dataProviderName中声明为foo时会调用foodata provider 的getMany而不会调用默认的。此外在资源未显式指定 data provider 时dataProviderName默认为default见 useMany.ts 的类型注释。queryOptionsqueryOptions用于向useQueryHook 传递额外选项例如useMany({ queryOptions: { retry: 3, enabled: false, }, });从源码看queryOptions的类型为MakeOptionalUseQueryOptions..., queryKey | queryFnuseMany.ts也就是说queryKey和queryFn是可选的——它们由useMany内部生成但你可以通过queryOptions覆盖它们。完整选项列表参见useQuery文档。测试用例验证了覆盖行为useMany.spec.tsx传入queryOptions.queryKey: [foo, bar]后data provider 收到的meta.queryKey为[foo, bar]且查询缓存中以[foo, bar]作为键传入queryOptions.queryFn后getMany不会被调用而是使用自定义的queryFn。metameta是一个特殊属性可用于向 data provider 方法传递额外信息用途包括针对特定用例自定义 data provider 方法使用纯 JavaScript 对象JSON生成 GraphQL 查询。在下面的示例中我们将headers属性放在meta对象中传递给getMany方法。按照类似的逻辑你可以传递任意属性来专门处理 data provider 方法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, }; }, //... };更多信息参见 General Concepts 文档的 meta 概念章节。在 documentation/docs/guides-concepts/general-concepts/index.md 中说明meta可以从三个来源填充——资源定义resources[].meta、Hook 参数useMany({ meta })、URL 参数——它们会被合并为单个 meta 属性提供给 provider 和 UI 集成使用。源码中useMany通过useMeta()合并 metauseMany.tsconst getMeta useMeta(); const preferredMeta meta; // ... const combinedMeta getMeta({ resource, meta: preferredMeta });测试用例验证useMany.spec.tsx资源中定义的meta: { foo: bar }会被透传给getMany方法。successNotification需要NotificationProvider才能使用该属性。数据成功获取后useMany会调用NotificationProvider的open函数显示成功通知。通过该属性可以自定义成功通知useMany({ successNotification: (data, ids, resource) { return { message: ${data.title} Successfully fetched., description: Success with no errors, type: success, }; }, });通知回调的签名接受三个参数dataGetManyResponseTData、idsBaseKey[]和resource资源标识。默认值为false即默认不显示成功通知见文档 API Reference 中的successNotification-defaultfalse。测试用例验证了useMany.spec.tsx自定义的successNotification会在成功时调用open并传入自定义配置若回调返回false则不显示通知第 442-469 行。errorNotification需要NotificationProvider才能使用该属性。数据获取失败后useMany会调用NotificationProvider的open函数显示错误通知。通过该属性可以自定义错误通知useMany({ errorNotification: (data, ids, resource) { return { message: Something went wrong when getting ${data.id}, description: Error, type: error, }; }, });默认的错误通知消息为Error (status code: statusCode)。测试用例验证了默认行为与自定义行为useMany.spec.tsx当getMany抛出错误时open会收到message: Error (status code: undefined)、key: 1-posts-getMany-notification、type: error的默认配置。liveMode需要LiveProvider才能使用该属性。决定收到相关实时事件时是否自动更新数据auto还是手动更新manual。它可用于在整个应用中实时更新和展示数据useMany({ liveMode: auto, });onLiveEvent需要LiveProvider才能使用该属性。当订阅的新事件到达时执行的回调函数useMany({ onLiveEvent: (event) { console.log(event); }, });liveParams需要LiveProvider才能使用该属性。传递给 live provider 的 subscribe 方法的额外参数。从源码看它会被展开到useResourceSubscription的params中useMany.ts 中的...liveParams用于定制订阅行为。overtimeOptions如果你希望为请求增加加载超时检测可以给该 Hook 传入overtimeOptions属性。当请求耗时过长时它对于展示加载指示器非常有用。interval是毫秒为单位的时间间隔onInterval是每个间隔被调用的函数。从该 Hook 返回overtime对象其elapsedTime为毫秒单位的已用时间当请求完成时变为undefined。const { overtime } useMany({ //... overtimeOptions: { interval: 1000, onInterval(elapsedInterval) { console.log(elapsedInterval); }, }, }); console.log(overtime.elapsedTime); // undefined, 1000, 2000, 3000 4000, ... // 你可以这样使用它 { elapsedTime 4000 divthis takes a bit longer than expected/div; }对应的实现位于useLoadingOvertime中测试用例 useMany.spec.tsx 验证了当请求耗时超过interval时onInterval被调用且elapsedTime按间隔递增请求完成后elapsedTime恢复为undefined。返回值Return ValuesuseMany返回一个包含 TanStack QueryuseQuery返回值类型为QueryObserverResult{ data: TData[]; error: TError }的对象并额外增加以下内容更多信息参见useQuery文档。query 与 result从源码类型定义看useMany.ts返回对象包含两个与数据相关的字段export type UseManyReturnTypeTData, TError { query: QueryObserverResultGetManyResponseTData, TError; result: { data: TData[]; }; } UseLoadingOvertimeReturnType;query完整的 TanStack Query 结果对象包含isLoading、isError、isSuccess、isPending、fetchStatus等全部状态result精简的数据访问入口result.data直接给出TData[]数组源码中为queryResponse?.data?.data || EMPTY_ARRAY其中EMPTY_ARRAY是冻结的空数组常量见 useMany.ts。这也是基本用法示例中const { result, query } useMany(...)的由来。overtime附加返回值overtime对象从该 Hook 返回。elapsedTime是毫秒为单位的已用时间请求完成时变为undefinedconst { overtime } useMany(); console.log(overtime.elapsedTime); // undefined, 1000, 2000, 3000 4000, ...API 参考API ReferenceProperties属性总表属性类型默认值说明resource必填string—传递给getMany的资源名或identifierids必填BaseKey[]—要获取的记录 id 数组queryOptionsUseManyQueryOptions—透传给 TanStack QueryuseQuery的选项metaMetaQuery—传递给 data provider 方法的额外信息dataProviderNamestringdefault多 data provider 时指定使用哪一个successNotificationSuccessErrorNotificationfalse自定义成功通知errorNotificationSuccessErrorNotificationError (status code: statusCode)自定义错误通知liveModeauto \| manual \| off—实时更新模式onLiveEvent(event) void—实时事件回调liveParamsobject—传给 live providersubscribe的参数overtimeOptions{ interval, onInterval }—加载超时检测配置Type Parameters类型参数属性说明类型默认值TQueryFnData查询函数返回的结果数据类型继承自BaseRecordBaseRecordBaseRecordTError继承自HttpError的自定义错误对象HttpErrorHttpErrorTDataselect函数返回的结果数据类型继承自BaseRecord。未指定时默认使用TQueryFnData的值BaseRecordTQueryFnData其中BaseRecord定义为{ id?: BaseKey; [key: string]: any }HttpError定义为{ message: string; statusCode: number; errors?: ValidationErrors; [key: string]: any }见 interface-references。Return Values返回值说明类型TanStack QueryuseQuery的结果QueryObserverResult{ data: TData[]; error: TError }overtime{ elapsedTime?: number }实战在自定义 data provider 中实现 getMany为了让useMany发挥批量请求的优势最佳实践是让 data provider 实现getMany。仓库中 packages/simple-rest/src/provider.ts 给出了一个最直观的参考实现getMany: async ({ resource, ids, meta }) { const { headers, method } meta ?? {}; const requestMethod (method as MethodTypes) ?? get; const { data } await httpClientrequestMethod}, { headers }, ); return { data, }; },该实现将ids序列化为查询字符串?id1id2id3向 API 发起单次请求并通过meta.headers支持自定义请求头、通过meta.method支持自定义 HTTP 方法——这正好呼应了上文meta属性的用法。如果你的后端只支持逐个查询也可以不实现getManyuseMany会自动降级为逐 id 调用getOne但正如文档所强调的这会导致 N 次请求应尽量避免。小结useMany是 Refine 数据获取体系中对批量读场景的标准化封装它以 TanStack Query 为基础提供缓存与请求控制以getMany为默认数据通路并内置getOne回退同时将通知、实时订阅、超时检测、meta透传等 Refine 横切能力无缝集成。掌握它的属性体系resource、ids、dataProviderName、queryOptions、meta、通知与实时相关属性、overtimeOptions与返回结构query、result、overtime你就能在项目中写出既高效又具备实时能力的批量数据加载逻辑。【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考