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小节的交互式演示。
文件由两部分组成:
- 一段 CSS 前置块(标记为
css live shared),仅设置演示容器body { padding: 4px; background: white; },属于文档渲染样式,与业务逻辑无关; - 一段 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 | 自定义错误对象类型,扩展HttpError | HttpError |
示例中传入IProduct与HttpError,意味着data?.data被推导为IProduct[],访问product.name、product.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 一节,这是唯一必填项:它会被原样传给dataProvider的getList方法,通常作为 API 端点的路径片段。也就是说,最简一行useList({ resource: "products" })就完成了“拉取该资源全量列表”的完整请求链路,其余能力(分页、排序、过滤、实时)都是在此之上的可选扩展。
三、useList 的底层原理:它是如何用 getList 驱动 useQuery 的
useList API 文档 开篇明确了useList的两个核心设计:
- 它是 TanStack Query
useQuery的扩展版本,支持useQuery的全部能力(缓存、重试、失效、DevTools 观察等); - 它以
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: ["*"]订阅该资源的所有实时事件,并把pagination、sorters、filters、liveParams等作为订阅参数传递(useList.ts#L183-L203)。这正是 v3 文档 “Realtime Updates” 一节所述“挂载时调用liveProvider.subscribe” 的实现落点,liveMode/onLiveEvent/liveParams参数也在此处被消费; - 缓存与选择:源码用
useMemo固化select函数,并在客户端分页模式下对data.data做slice((current - 1) * pageSize, current * pageSize)切片(useList.ts#L208-L219)。从源码结构看,v3 文档中config.hasPagination控制“是否启用服务端分页”的语义,在当前实现中已演化为pagination.mode("server"/"client")的显式分页模式判定(useList.ts#L163-L166); - 错误归一化:
useOnError()提供的checkError与useHandleNotification()负责把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 | 过滤条件数组,结构为CrudFilters(field/operator/value),动态变化会触发新请求 | useList({ config: { filters: [{ field: "title", operator: "contains", value: "Foo" }] } }) |
config.sort | 排序条件数组,结构为CrudSorting(field/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";需LiveProvider | useList({ 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 QueryuseQuery的QueryObserverResult,即基础示例中解构data/isLoading/isError的来源;- 数据负载类型为
{ data: TData[]; total: number },与getList的GetListResponse结构一致; - 因此所有
useQuery的原生能力(refetch、isFetching、dataUpdatedAt等)在useList上同样可用。
六、版本差异提示:v3 文档与当前仓库源码的对应关系
引用本文时请注意适用前提:指定文档属于version-3.xx.xx版本文档,其source字段指向 v3 分支的packages/core/src/data/hooks/useList.ts;而当前仓库主干的实现在 packages/core/src/hooks/data/useList.ts,两者存在可辨识的 API 演化,从源码签名可以直接确认:
- 参数扁平化:v3 的
config: { filters, sort, pagination, hasPagination }嵌套结构,在主干源码中演化为扁平的顶层参数filters/pagination/sorters/meta(见 useList.ts#L125-L138 的参数解构); metaData更名为meta:v3 示例中的metaData在主干中为meta,并与全局 meta 经useMeta()合并(getMeta({ resource, meta }),useList.ts#L168);- 分页模式显式化:
handlePaginationParams将分页归一化为带mode的对象,mode === "server"决定hasPagination语义(useList.ts#L163-L166); - select 记忆化与客户端分页切片:源码注释明确提示用户若自定义
queryOptions.select应自行useCallback包裹以避免每次渲染重跑(useList.ts#L205-L207)。
此外,当前主干文档目录中保留了同一基础用法示例的最新版文件(documentation/docs/data/hooks/use-list/_basic-usage-live-preview.md),可作为 v3 写法向新版写法迁移的对照参考。
七、实践要点小结
- 最小可运行单元:
useList({ resource: "products" })即完成一次完整的getList请求;返回值的列表在data?.data,务必保留?? []兜底; - 三态渲染是列表页标准姿势:
isLoading前置渲染加载态、isError渲染错误态、成功态再遍历data.data,这与 Live Preview 示例的结构完全一致; - 动态参数即新请求:修改
filters/sort/pagination会改变 query key 并触发重新拉取,这正是把分页器、过滤器 UI 与useList联动的机制基础; - 实时与通知是可选叠加层:只有配置了
LiveProvider/NotificationProvider后,liveMode、onLiveEvent、successNotification等参数才会产生实际效果; - 版本对齐:按 v3 文档开发时以本文第四节的
config嵌套写法为准;若仓库源码已升级到新版 API,则以扁平参数(filters/sorters/meta)为准,避免两套写法混用。
综合而言,basic-usage-live-preview.md 虽然只是一个 60 行的演示片段,但它浓缩了useList的基础用法契约:resource驱动getList、useQuery语义的返回结构、以及三态渲染模式。理解这三点,再配合本文第三节所述的源码级实现机制,即可在 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),仅供参考