ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

react-admin 数据获取核心 Hook:useGetList 完整实战指南

react-admin 数据获取核心 Hook:useGetList 完整实战指南 react-admin 数据获取核心 HookuseGetList 完整实战指南【免费下载链接】react-adminA frontend Framework for single-page applications on top of REST/GraphQL APIs, using TypeScript, React and Material Design项目地址: https://gitcode.com/gh_mirrors/re/react-adminuseGetList是 react-admin 中最常用的数据获取 Hook它在组件挂载时调用dataProvider.getList()用于拉取记录列表并原生支持分页pagination、排序sort与过滤filter。本文基于 react-admin 官方文档与 ra-core 源码从语法签名、请求参数、返回值、渲染接入、缓存机制到 TypeScript 用法系统讲解该 Hook 的使用方式与底层实现帮助你写出数据驱动的列表、下拉选单与自定义页面。useGetList 是什么useGetList是 react-admin 提供的一个 React Hook封装了数据提供器Data Provider的getList()方法。它遵循 react-query 的声明式数据获取模型你只需声明要什么数据Hook 便自动处理请求生命周期、加载状态、错误状态、缓存与重试。它的定位非常明确组件挂载时自动发起请求无需手动触发完整支持分页、排序、过滤与meta扩展参数返回值随请求状态实时更新包含isPending、error、refetch等状态字段基于 react-query 缓存同参数重复调用会命中缓存。从源码看useGetList的实现位于 packages/ra-core/src/dataProvider/useGetList.ts并通过 packages/ra-core/src/dataProvider/index.ts 对外导出最终由react-admin包统一暴露。它的核心实现就是一层对 TanStack QueryuseQuery的封装const result useQueryGetListResultRecordType, ErrorType, GetListResultRecordType({ queryKey: [resource, getList, { pagination, sort, filter, meta }], queryFn: queryParams dataProvider.getListRecordType(resource, { pagination, sort, filter, meta, signal: dataProvider.supportAbortSignal true ? queryParams.signal : undefined, }) .then(({ data, total, pageInfo, meta }) ({ data, total, pageInfo, meta })), ...queryOptions, });语法签名useGetList接收三个参数资源名、请求参数对象与 react-query 选项对象。const { data, total, isPending, error, refetch, meta } useGetList( resource, { pagination: { page, perPage }, sort: { field, order }, filter, meta }, options );参数一resource字符串类型的资源名称例如posts、comments。它决定了请求发送到哪个资源端点。参数二请求参数均有默认值参数类型默认值说明pagination{ page: number, perPage: number }{ page: 1, perPage: 25 }分页参数page为页码从 1 开始perPage为每页条数sort{ field: string, order: ASC \| DESC }{ field: id, order: DESC }排序参数field为字段名order为排序方向filterobject{}过滤条件键值对形式例如{ title: hello, world }metaanyundefined可选任何你想传给数据提供器的附加数据这些默认值在源码中有明确实现useGetList.tsconst { pagination { page: 1, perPage: 25 }, sort { field: id, order: DESC }, filter {}, meta, } params;需要注意即使你不传任何参数请求也会携带默认的分页与排序。对应地GetListParams类型定义于 packages/ra-core/src/types.ts包含pagination、sort、filter、meta与signal五个可选字段。参数三optionsoptions是可选的会被原样透传给 TanStack Query v5 的useQuery。可用的选项包括cacheTime、enabled、initialData、initialDataUpdatedAt、isDataEqual、keepPreviousData、meta、notifyOnChangeProps、notifyOnChangePropsExclusions、onError、onSettled、onSuccess、placeholderData、queryKeyHashFn、refetchInterval、refetchIntervalInBackground、refetchOnMount、refetchOnReconnect、refetchOnWindowFocus、retry、retryOnMount、retryDelay、select、staleTime、structuralSharing、suspense、useErrorBoundary其中onSuccess、onError、onSettled三个副作用回调是 react-admin 在透传时单独解构处理并保证稳定引用的通过useEvent包装见 useGetList.ts。而enabled: false可用于延迟请求、staleTime控制数据新鲜度、refetchOnWindowFocus控制窗口聚焦时的自动刷新等这些都是日常高频使用的选项。Query Key 与缓存该 Hook 的 react-query query key 为[resource, getList, { pagination, sort, filter, meta }]这意味着只要resource、pagination、sort、filter、meta中的任何一个发生变化query key 就会变化react-query 会将其视为不同的查询并重新发起请求。同一 key 的查询则共享缓存。这条规则同样被单元测试所验证资源名从posts变为comments时dataProvider.getList会被再次调用见 packages/ra-core/src/dataProvider/useGetList.spec.tsx。基础用法获取记录列表useGetList最常见的应用场景是组件挂载后立即获取列表数据并渲染。import { useGetList } from react-admin; const LatestNews () { const { data, total, isPending, error } useGetList( posts, { pagination: { page: 1, perPage: 10 }, sort: { field: published_at, order: DESC } } ); if (isPending) { return Loading /; } if (error) { return pERROR/p; } return ( h1Latest news/h1 ul {data.map(record li key{record.id}{record.title}/li )} /ul p{data.length} / {total} articles/p / ); };这里data是记录数组每条记录必须有id字段total是数据提供器返回的记录总数isPending为 true 时表示请求尚未完成。返回值详解useGetList的返回值是 TanStack Query 结果与getList响应合并后的对象useGetList.ts字段类型说明dataRecordType[]当前页记录数组totalnumber \| undefined满足条件的记录总数数据提供器不返回时不存在pageInfo{ hasNextPage?: boolean, hasPreviousPage?: boolean }部分分页模式下判断前后页是否存在metaany数据提供器随响应返回的附加元数据isPendingboolean首次请求尚未完成时为 trueisFetchingboolean请求含后台刷新进行中为 trueerrorError \| null请求失败时的错误对象refetch() void手动重新发起请求的函数其余字段—TanStack QueryuseQuery返回的全部属性其中total、pageInfo、meta三个字段的类型定义在 GetListResult 中。请求失败时data可能为undefined因此渲染前务必先处理isPending与error两个分支这与测试中验证的状态流转一致useGetList.spec.tsx。在 react-admin 迭代组件中渲染数据如果要把useGetList的结果交给DataTable、SimpleList、SingleFieldList这类 react-admin 迭代组件渲染你需要先把数据放进一个ListContext。useListHook 就是为此设计的——它接收原始数据生成完整的列表控制器上下文包括分页、排序、选中状态等import { useGetList, useList, ListContextProvider, DataTable, DateField, Pagination } from react-admin; const LatestNews () { const { data, isPending, error } useGetList( posts, { pagination: { page: 1, perPage: 100 } }, ); if (error) { return pERROR/p; } const listContext useList({ data, isPending, perPage: 10, sort: { field: published_at, order: DESC } }); return ( ListContextProvider value{listContext} h1Latest news/h1 DataTable DataTable.Col sourcetitle / DataTable.Col sourcepublished_at field{DateField} / DataTable.NumberCol sourceviews / /DataTable Pagination / /ListContextProvider ); };在这个例子中useGetList一次性拉取全部文章useList在客户端完成每页 10 条的展示、点击列头排序等交互Pagination提供翻页控件。这是服务端只负责取数、客户端负责交互的典型组合。useList的详细参数说明可参考 docs/useList.mdListContext的内容见 docs/useListContext.md。向数据提供器传递额外参数meta 参数如果你需要向数据提供器传递getList标准参数之外的附加信息可以通过第二个参数里的meta字段。例如你的数据提供器支持embed参数来内嵌关联记录const { data, total, isPending, error } useGetList( posts, { pagination: { page: 1, perPage: 10 }, sort: { field: published_at, order: DESC }, // 通过 meta 参数传递附加信息 meta: { embed: [author, category] } } );meta会被原样传给dataProvider.getList()的第二个参数数据提供器可以据此定制请求如拼接 URL 查询串、选择返回字段等。单元测试验证了meta的传递路径useGetList.spec.tsxexpect(dataProvider.getList).toBeCalledWith(posts, { filter: {}, pagination: { page: 1, perPage: 20 }, sort: { field: id, order: DESC }, meta: { hello: world }, signal: undefined, });注意不要把这个meta参数与响应里的meta属性混淆——虽然同名但它们毫不相关。请求meta是你发给数据提供器的响应meta是数据提供器返回给你的。读取响应中的附加元数据如果后端在返回记录之外还附带了一些额外信息可以通过返回值的meta属性访问const { data, total, isPending, error, // 响应中的附加信息 meta } useGetList(posts, { pagination: { page: 1, perPage: 10 }});同样地这个响应meta与请求meta参数无任何关系。它来自getList()响应对象在 useGetList.ts 中通过解构{ data, total, pageInfo, meta }被保留下来。部分分页后端不返回 total 的场景部分后端统计总条数count代价昂贵此时数据提供器可以在响应中省略total改用pageInfo告知是否有上一页/下一页。这一数据提供器约定详见 docs/DataProviderWriting.md响应示例形如// { // data: [ ... ], // pageInfo: { // hasPreviousPage: false, // hasNextPage: true, // } // }useGetList会把pageInfo透传给你据此实现加载更多式的翻页import { useState } from react; import { useGetList } from react-admin; const LatestNews () { const [page, setPage] useState(1); const { data, pageInfo, isPending, error } useGetList( posts, { pagination: { page, perPage: 10 }, sort: { field: published_at, order: DESC } } ); if (isPending) { return Loading /; } if (error) { return pERROR/p; } const { hasNextPage, hasPreviousPage } pageInfo; const getNextPage () setPage(page 1); return ( h1Latest news/h1 ul {data.map(record li key{record.id}{record.title}/li )} /ul {hasNextPage button onClick{getNextPage}More articles/button} / ); };注意此例中pageInfo可能为undefined当数据提供器返回的是total时解构前建议做防御性处理。另外react-admin 的Pagination组件会自动识别pageInfo并渲染对应的分页控件。如果你的目标是向下滚动加载历史数据、旧页面保留在屏幕上类似社交媒体信息流也可以改用useInfiniteGetListHook它会自动拼接已加载的各页数据。获取关联记录优先使用 ReferenceManyField当你想获取与某条记录关联的另一批记录例如一篇文章下的所有评论时建议直接使用ReferenceManyField组件。它会自动处理加载状态并在数据拉取期间显示加载指示器import { ReferenceManyField } from react-admin; const PostComments () { return ( ReferenceManyField referencecomments targetpost_id DataTable DataTable.Col sourcecreated_at field{DateField} / DataTable.Col sourceauthor / DataTable.Col sourcebody / /DataTable /ReferenceManyField ); };它等价于下面这段手动使用useGetListuseRecordContextuseList的代码import { useGetList } from react-admin; const PostComments () { const record useRecordContext(); const { data, isPending, error } useGetList( comments, { filter: { post_id: record.id } } ); if (isPending) { return Loading /; } if (error) { return pERROR/p; } const listContext useList({ data }); return ( ListContextProvider value{listContext} DataTable DataTable.Col sourcecreated_at field{DateField} / DataTable.Col sourceauthor / DataTable.Col sourcebody / /DataTable /ListContextProvider ); };两者效果一致但ReferenceManyField帮你省去了加载状态、上下文组装等样板代码是更推荐的方式。只有当你需要完全自定义取数与交互逻辑时才直接用useGetList手动实现。手动刷新列表useGetList返回的refetch函数可以强制重新发起请求import { useGetList } from react-admin; const LatestNews () { const { data, total, isPending, error, refetch } useGetList(/* ... */); if (isPending) { return Loading /; } if (error) { return pERROR/p; } return ( h1Latest news/h1 ul {data.map(record li key{record.id}{record.title}/li )} /ul p{data.length} / {total} articles/p button onClick{() refetch()}Refresh/button / ); };refetch是 TanStack Query 原生提供的点击按钮即可在任意时刻刷新列表数据。实时更新改用 useGetListLive如果你希望列表在数据变更时自动更新订阅resource/[resource]主题可以改用实时数据 HookuseGetListLive来自react-admin/ra-realtime商业包。把useGetList替换为useGetListLive即可调用方式几乎完全一致-import { useGetList } from react-admin; import { useGetListLive } from react-admin/ra-realtime; const LatestNews () { - const { data, total, isPending, error } useGetList(posts, { const { data, total, isPending, error } useGetListLive(posts, { pagination: { page: 1, perPage: 10 }, sort: { field: published_at, order: DESC }, }); if (isPending) { return Loading /; } if (error) { return pERROR/p; } return ( ul {data.map(item ( li key{item.id}{item.title}/li ))} /ul ); };此后每当有记录被创建、更新或删除data都会自动更新无需手动refetch。TypeScript 泛型支持useGetList接受一个记录类型的泛型参数让data获得完整的类型提示import { useGetList } from react-admin; type Post { id: number; title: string; }; const LatestNews () { const { data: posts, total, isPending, error } useGetListPost( posts, { pagination: { page: 1, perPage: 10 }, sort: { field: published_at, order: DESC } } ); if (isPending) { return Loading /; } if (error) { return pERROR/p; } return ( h1Latest news/h1 ul {/* TypeScript 知道 posts 的类型是 Post[] */} {posts.map(post li key{post.id}{post.title}/li )} /ul p{posts.length} / {total} articles/p / ); };泛型参数约束为RaRecord即至少包含id: Identifier的对象其完整签名见 useGetList.ts。源码级的缓存与取消行为深入源码可以发现useGetList还有两个容易被忽略但非常实用的行为1. 预填充 getOne 缓存请求成功后如果返回的记录数不超过 100 条常量MAX_DATA_LENGTH_TO_CACHE 100useGetList会把每条记录写入对应资源的getOne查询缓存useGetList.ts。这意味着列表页加载过的记录进入详情页时无需重新请求即可立即显示。此行为在 useGetList.spec.tsx 与超过 100 条不预填充useGetList.spec.tsx均有测试覆盖。2. 支持请求取消AbortSignal当数据提供器声明supportAbortSignal true时useGetList会把 TanStack Query 的取消信号透传给getList()useGetList.ts。查询被取消例如组件卸载、key 变化时请求会被中止避免无用的网络开销测试见 useGetList.spec.tsx。3. 副作用回调稳定性onSuccess/onError/onSettled通过useEvent包装始终保持稳定引用不会因闭包问题导致副作用重复触发useGetList.ts。常见问题与建议渲染前必须处理isPending与error请求未完成或失败时data为undefined直接访问data.map会报错先做分支处理是标准姿势。同一资源不同参数会各自缓存query key 包含完整参数翻页、改排序都会生成新查询这也意味着频繁切换筛选条件会产生多个缓存条目可通过staleTime平衡新鲜度与请求次数。服务端取数 vs 客户端交互useGetList只负责从服务端取数分页、排序、选中这类交互逻辑交给useList或ListContext体系二者职责清晰。关联数据优先用ReferenceManyField省去样板代码还能自动获得加载指示器。需要实时更新时考虑useGetListLive商业包或自行结合useSubscribe。useGetList是构建 react-admin 自定义页面、仪表盘、下拉选单数据源的基础设施。理解它的参数语义、缓存规则与源码行为能让你在编写数据密集型界面时少走弯路。相关的配套文档包括DataProviderWriting、useList、useListContext、useInfiniteGetList 与 useGetListLive都可以在本仓库docs/目录下继续深入阅读。【免费下载链接】react-adminA frontend Framework for single-page applications on top of REST/GraphQL APIs, using TypeScript, React and Material Design项目地址: https://gitcode.com/gh_mirrors/re/react-admin创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进