Refine useList 基础用法实战:从 Live Preview 到 dataProvider.getList 的完整解析
2026/9/14 13:13:57 网站建设 项目流程

Refine useList 基础用法实战:从 Live Preview 到 dataProvider.getList 的完整解析

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

本文围绕 Refine v3 API 参考中useList钩子的基础用法(Basic Usage)展开,完整剖析其 Live Preview 示例中每一段代码的职责,并进一步结合仓库中useList的源码实现与测试用例,讲清它是如何作为 TanStack QueryuseQuery的扩展、以dataProvider.getList作为查询函数、通过 query key 缓存数据,以及加载/错误/成功三态渲染的标准写法。读完本文,你可以独立在任何 Refine 项目中用useList拉取资源列表数据,并理解其底层的订阅、缓存与实时(Live)事件机制。

一、基础用法示例:这段 Live Preview 在演示什么

在 Refine v3 文档的 API 参考中,useList的“基础用法”部分由一个独立的 Live Preview 组件文件承载,其完整源码位于 basic-usage-live-preview.md。该文件被 useList API 文档 以import BasicUsageLivePreview from "./basic-usage-live-preview.md";的方式引入,作为文档中## Basic Usage小节的交互式演示。

文件由两部分组成:

  1. 一段 CSS 前置块(标记为css live shared),仅设置演示容器body { padding: 4px; background: white; },属于文档渲染样式,与业务逻辑无关;
  2. 一段 TSX 演示块,标记为tsx live url=http://localhost:3000/products previewHeight=300px,表示该 Live Preview 挂载在本地 mock 服务http://localhost:3000/products资源上,预览高度 300px。

其中 TSX 演示块的结构是文档体系中 Live Preview 的标准骨架:

setInitialRoutes(["/products"]); // visible-block-start import { useList, HttpError } from "@pankod/refine-core"; interface IProduct { id: number; name: string; material: string; } const ProductList: React.FC = () => { const { data, isLoading, isError } = useList<IProduct, HttpError>({ resource: "products", }); const products = data?.data ?? []; if (isLoading) { return <div>Loading...</div>; } if (isError) { return <div>Something went wrong!</div>; } return ( <ul> {products.map((product) => ( <li key={product.id}> <h4> {product.name} - ({product.material}) </h4> </li> ))} </ul> ); }; // visible-block-end setRefineProps({ resources: [ { name: "products", list: ProductList, }, ], }); render(<RefineHeadlessDemo />);

几个关键细节:

  • // visible-block-start/// visible-block-end注释对之间才是文档页面上真正“可见”的示例代码。文档插件会裁剪掉这对注释之外的内容,因此读者在 Refine 文档站看到的“基础用法”示例就是中间那段useList调用代码,这也是理解该文件内容的核心边界;
  • setInitialRoutes(["/products"])setRefineProps(...)render(<RefineHeadlessDemo />)是文档 Live Preview 基建提供的辅助函数:前者设定演示应用的初始路由,后者把ProductList注册为products资源的列表页组件并渲染一个无 UI 框架的 Headless 演示应用。这些不属于useList本身的 API;
  • resource: "products"与 mock 服务的/products端点一一对应,演示数据即 Refine Headless Demo 的标准商品集合(id/name/material字段)。

二、基础用法代码逐段解读

2.1 泛型参数:useList<IProduct, HttpError>

useList的调用签名为useList<TData, TError>(v3 API 参考中的 Type Parameters 一节说明):

类型参数含义默认值
TData查询结果数据的元素类型,扩展BaseRecord(即{ id: number \| string; [key: string]: any }约束)BaseRecord
TError自定义错误对象类型,扩展HttpErrorHttpError

示例中传入IProductHttpError,意味着data?.data被推导为IProduct[],访问product.nameproduct.material时有完整类型提示;而错误分支中的错误对象则带有statusCode等 HTTP 语义字段。这与 v3 文档中 Return Values 一节的声明一致:useList返回的是 TanStack QueryuseQuery的结果对象,其中data的结构为{ data: TData[]; total: number }

2.2 返回值三态处理

useList返回useQuery的标准结果集,示例演示了列表页最核心的三态处理模式:

const { data, isLoading, isError } = useList<IProduct, HttpError>({ resource: "products", }); const products = data?.data ?? []; if (isLoading) return <div>Loading...</div>; if (isError) return <div>Something went wrong!</div>; // ...渲染 products

要点:

  • 数据不在data顶层,而在data?.data(列表数组)与data.total(总数)之中——这是 v3getList响应的约定结构{ data: TData[]; total: number }const products = data?.data ?? []的兜底写法避免了undefined上的map调用;
  • isLoading/isError直接来自 TanStack Query 的QueryObserverResult,无需二次封装;
  • 成功分支中用product.id作为key,符合 React 列表渲染规范。

2.3 最简调用:只传resource

基础示例只传了一个必填参数resource: "products"。按照 v3 API 文档的 Properties 一节,这是唯一必填项:它会被原样传给dataProvidergetList方法,通常作为 API 端点的路径片段。也就是说,最简一行useList({ resource: "products" })就完成了“拉取该资源全量列表”的完整请求链路,其余能力(分页、排序、过滤、实时)都是在此之上的可选扩展。

三、useList 的底层原理:它是如何用 getList 驱动 useQuery 的

useList API 文档 开篇明确了useList的两个核心设计:

  1. 它是 TanStack QueryuseQuery的扩展版本,支持useQuery的全部能力(缓存、重试、失效、DevTools 观察等);
  2. 它以dataProvider.getList作为 query function,并使用由传入属性生成的query key进行缓存——在 TanStack Query devtools 中可以直接看到该 key。

结合当前仓库中的源码(v3 文档source字段指向 v3 分支的packages/core/src/data/hooks/useList.ts,当前主干中的对应实现已迁移到 packages/core/src/hooks/data/useList.ts),可以验证上述两条设计在代码层面的落点:

  • 资源与数据源解析useList首先通过useResourceParams解析资源名(props 传入优先),再经由useDataProvider()pickDataProvider(identifier, dataProviderName, resources)选出实际生效的dataProvider。这解释了 v3 文档中dataProviderName参数的用途:当应用配置了多个 dataProvider 时,用它指定由哪一个提供getList(见 useList.ts#L144-L159);
  • 查询函数即 getList:源码中const { getList } = dataProvider(pickedDataProvider);(useList.ts#L181)解构出查询函数,filters/pagination/sorters/meta等参数会经notificationValues之类的归一化对象一并交给getList(useList.ts#L170-L176)。因此“动态改变pagination/sort/filters会触发新请求”这一文档结论,本质上是这些参数进入了 query key 与请求参数;
  • Live 订阅内建useList内部调用了useResourceSubscription,以channel: "resources/${resource?.name}"types: ["*"]订阅该资源的所有实时事件,并把paginationsortersfiltersliveParams等作为订阅参数传递(useList.ts#L183-L203)。这正是 v3 文档 “Realtime Updates” 一节所述“挂载时调用liveProvider.subscribe” 的实现落点,liveMode/onLiveEvent/liveParams参数也在此处被消费;
  • 缓存与选择:源码用useMemo固化select函数,并在客户端分页模式下对data.dataslice((current - 1) * pageSize, current * pageSize)切片(useList.ts#L208-L219)。从源码结构看,v3 文档中config.hasPagination控制“是否启用服务端分页”的语义,在当前实现中已演化为pagination.mode"server"/"client")的显式分页模式判定(useList.ts#L163-L166);
  • 错误归一化useOnError()提供的checkErroruseHandleNotification()负责把getList抛出的异常统一转换为TError(默认HttpError)并驱动错误通知,这与 v3 文档中errorNotification的默认值"Error (status code: statusCode)"相呼应。

测试侧同样印证了基础用法的契约:useList.spec.tsx 中以useList<{ id: number }>({ ... })的形式渲染钩子并断言其返回结构与请求行为,是验证“只传resource也能完成完整查询链路”的自动化依据。

四、v3 API 参数速查(基础用法之上的可选扩展)

基础示例只用到resource,但实际项目中列表页几乎总会追加以下参数。以下为 v3 API 参考(useList index.md)定义的完整参数面,全部最终都会传递给dataProvider.getList

参数说明示例
resource(必填)传给getList的资源名,通常作为端点路径useList({ resource: "categories" })
dataProviderName多 dataProvider 场景下指定使用哪一个useList({ dataProviderName: "second-data-provider" })
config.filters过滤条件数组,结构为CrudFiltersfield/operator/value),动态变化会触发新请求useList({ config: { filters: [{ field: "title", operator: "contains", value: "Foo" }] } })
config.sort排序条件数组,结构为CrudSortingfield/order: "asc" \| "desc"),动态变化会触发新请求useList({ config: { sort: [{ field: "title", order: "asc" }] } })
config.pagination分页参数,含current(当前页码)与pageSize(每页条数)useList({ config: { pagination: { current: 2, pageSize: 20 } } })
config.hasPagination是否启用服务端分页;设为false表示不分页useList({ config: { hasPagination: false } })
queryOptions透传给底层useQuery的额外选项(重试、失效策略等)useList({ queryOptions: { retry: 3 } })
metaData附加到 dataProvider 方法的元信息(如自定义请求头),也可用于以 JSON 对象生成 GraphQL 查询useList({ metaData: { headers: { "x-meta-data": "true" } } })
successNotification拉取成功后调用NotificationProvider.open的通知定制函数,签名为(data, values, resource) => { message, description, type };需配置NotificationProvider才生效见下方示例
errorNotification拉取失败后的错误通知定制函数,同上;默认展示"Error (status code: statusCode)"见下方示例
liveMode"auto"(收到 live 事件自动刷新)/"manual";需LiveProvideruseList({ liveMode: "auto" })
onLiveEvent订阅收到新事件时的回调useList({ onLiveEvent: (event) => console.log(event) })
liveParams透传给liveProvider.subscribe的额外参数

metaData的典型用法是在 dataProvider 侧读取并使用它。v3 文档给出的完整模式为:

useList({ metaData: { headers: { "x-meta-data": "true" }, }, }); const myDataProvider = { //... getList: async ({ resource, pagination, hasPagination, sort, filters, metaData }) => { const headers = metaData?.headers ?? {}; const url = `${apiUrl}/${resource}`; const { data } = await httpClient.get(`${url}`, { headers }); return { data }; }, //... };

通知定制的标准写法:

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

v3 文档同时给出了UseListConfig的类型定义,便于对照类型系统核对参数形态:

interface UseListConfig { hasPagination?: boolean; pagination?: { current?: number; pageSize?: number; }; sort?: Array<{ field: string; order: "asc" | "desc"; }>; filters?: Array<{ field: string; operator: CrudOperators; value: any; }>; }

五、返回值与类型契约

按照 v3 API 参考的 Return Values 与 API 一节:

  • useList返回TanStack QueryuseQueryQueryObserverResult,即基础示例中解构data/isLoading/isError的来源;
  • 数据负载类型为{ data: TData[]; total: number },与getListGetListResponse结构一致;
  • 因此所有useQuery的原生能力(refetchisFetchingdataUpdatedAt等)在useList上同样可用。

六、版本差异提示:v3 文档与当前仓库源码的对应关系

引用本文时请注意适用前提:指定文档属于version-3.xx.xx版本文档,其source字段指向 v3 分支的packages/core/src/data/hooks/useList.ts;而当前仓库主干的实现在 packages/core/src/hooks/data/useList.ts,两者存在可辨识的 API 演化,从源码签名可以直接确认:

  1. 参数扁平化:v3 的config: { filters, sort, pagination, hasPagination }嵌套结构,在主干源码中演化为扁平的顶层参数filters/pagination/sorters/meta(见 useList.ts#L125-L138 的参数解构);
  2. metaData更名为meta:v3 示例中的metaData在主干中为meta,并与全局 meta 经useMeta()合并(getMeta({ resource, meta }),useList.ts#L168);
  3. 分页模式显式化handlePaginationParams将分页归一化为带mode的对象,mode === "server"决定hasPagination语义(useList.ts#L163-L166);
  4. select 记忆化与客户端分页切片:源码注释明确提示用户若自定义queryOptions.select应自行useCallback包裹以避免每次渲染重跑(useList.ts#L205-L207)。

此外,当前主干文档目录中保留了同一基础用法示例的最新版文件(documentation/docs/data/hooks/use-list/_basic-usage-live-preview.md),可作为 v3 写法向新版写法迁移的对照参考。

七、实践要点小结

  1. 最小可运行单元useList({ resource: "products" })即完成一次完整的getList请求;返回值的列表在data?.data,务必保留?? []兜底;
  2. 三态渲染是列表页标准姿势isLoading前置渲染加载态、isError渲染错误态、成功态再遍历data.data,这与 Live Preview 示例的结构完全一致;
  3. 动态参数即新请求:修改filters/sort/pagination会改变 query key 并触发重新拉取,这正是把分页器、过滤器 UI 与useList联动的机制基础;
  4. 实时与通知是可选叠加层:只有配置了LiveProvider/NotificationProvider后,liveModeonLiveEventsuccessNotification等参数才会产生实际效果;
  5. 版本对齐:按 v3 文档开发时以本文第四节的config嵌套写法为准;若仓库源码已升级到新版 API,则以扁平参数(filters/sorters/meta)为准,避免两套写法混用。

综合而言,basic-usage-live-preview.md 虽然只是一个 60 行的演示片段,但它浓缩了useList的基础用法契约:resource驱动getListuseQuery语义的返回结构、以及三态渲染模式。理解这三点,再配合本文第三节所述的源码级实现机制,即可在 Refine 项目中稳定、类型安全地构建任意复杂的列表数据流。

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

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询