Refine 实战:useList 排序(sorters)与动态数据列表构建指南 📅 发布时间:2026/9/11 12:31:22 👁 浏览次数: Refine 实战useList 排序sorters与动态数据列表构建指南【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine本文以 Refine 官方文档中useList排序功能为核心结合 useList 完整文档 与仓库源码packages/core/src/hooks/data/useList.ts等系统讲解sorters属性的类型定义、传递链路、查询缓存机制以及 dataProvider 端的实际消费方式。读完本文你将能够在 Refine 应用中独立实现一个点击切换排序字段/方向、数据自动刷新的列表页并理解其底层原理。一、useList是什么useList是 Refine 提供的核心数据获取 Hook它是 TanStack Query 的useQuery的扩展版本支持其全部特性并在此基础上增加了与 Refine 数据层相关的能力。当需要按照**排序sorters、过滤filters、分页pagination**等条件从某个resource获取列表数据时useList就是最直接的入口参见 useList 文档。它的两个关键设计查询函数内部使用传给Refine组件的dataProvider的getList方法作为查询函数查询缓存根据传入的 properties 生成查询键query key数据会被缓存并可在 TanStack Query Devtools 中直接观察 query key 的变化。而本文的主角——排序就是通过useList的sorters属性实现的把sorters传给dataProvider.getList由数据提供方REST / GraphQL 等将其转换为对应的排序参数。二、排序功能演示一个可切换排序的商品列表Refine 文档为排序功能提供了完整的可运行示例即 排序 Live Preview 源码。完整代码示例如下import { useState } from react; import { useList, HttpError } from refinedev/core; interface IProduct { id: number; name: string; material: string; } const ProductList: React.FC () { const [order, setOrder] useStateasc | desc(asc); const { result, query } useListIProduct, HttpError({ resource: products, sorters: [ { field: name, order, }, ], }); const products result.data ?? []; if (query.isLoading) { return divLoading.../div; } if (query.isError) { return divSomething went wrong!/div; } return ( div button onClick{() setOrder((prev) (prev asc ? desc : asc))} toggle sort /button ul {products.map((product) ( li key{product.id} h4 {product.name} - ({product.material}) /h4 /li ))} /ul /div ); };这段示例虽然短小但涵盖了useList排序功能的全部核心要点sorters是一个对象数组每个对象由field字段名与order排序方向组成排序方向由 React state 驱动order是asc | desc联合类型点击 toggle sort 按钮在升序与降序之间切换动态改变sorters会触发新的请求当orderstate 变化导致sorters内容变化时useList会自动重新请求数据——这正是 Refine 文档明确指出的行为Dynamically changing thesortersproperty will trigger a new request.参见 useList 文档。对应的路由注册与渲染挂载代码如下setRefineProps({ resources: [ { name: products, list: /products, }, ], }); render( ReactRouter.BrowserRouter RefineHeadlessDemo ReactRouter.Routes ReactRouter.Route path/products element{ProductList /} / /ReactRouter.Routes /RefineHeadlessDemo /ReactRouter.BrowserRouter, );三、sorters的类型定义CrudSort与CrudSorting在 Refine 的核心类型中排序相关的类型定义位于 packages/core/src/contexts/data/types.tsexport type SortOrder desc | asc | null; export type CrudSort { field: string; order: asc | desc; }; export type CrudSorting CrudSort[];需要特别注意的是CrudSort的order字段在排序数组中仅接受asc | desc两种字面量因此当你把 state 声明为useStateasc | desc(asc)时类型系统就能保证sorters永远合法类型别名CrudSorting CrudSort[]表示整个排序条件数组一个列表请求可以同时携带多字段排序sorters: [ { field: name, order: asc }, { field: createdAt, order: desc }, ],与过滤类似useList的sorters属性注释明确指出其会被原样传给dataProvider的getList方法sorterswill be passed to thegetListmethod from thedataProvideras a parameter. It is used to send sort query parameters to the API.参见 useList 文档。四、源码级原理sorters在useList内部如何流转要理解改变排序自动刷新数据的机制需要阅读 useList 实现。其内部流程可以拆解为三条链路4.1 查询函数把sorters交给getListuseList在构造queryFn时会从解构出的prefferedSorters中把排序条件完整透传给数据提供方queryFn: (context) { const meta { ...combinedMeta, ...prepareQueryContext(context), }; return getListTQueryFnData({ resource: resource?.name ?? , pagination: prefferedPagination, filters: prefferedFilters, sorters: prefferedSorters, // 排序条件透传给 getList meta, }); },由此可见sorters与pagination、filters一样是getList参数中的一等公民。真正的查询执行完全由你配置的 dataProvider 决定。4.2 查询键sorters参与 query key 生成useList使用useKeys()构造稳定的查询键sorters是其中的一部分queryKey: keys() .data(pickedDataProvider) .resource(identifier ?? ) .action(list) .params({ ...(preferredMeta || {}), filters: prefferedFilters, ...(isServerPagination { pagination: prefferedPagination, }), ...(sorters { sorters, }), }) .get(),这正是动态改变sorters会触发新请求的根本原因查询键是查询缓存的指纹只要排序条件变化query key 就变化TanStack Query 便会视为一次新的查询并重新执行queryFn。这也是为什么文档建议可以用 TanStack Query Devtools 直接观察 query key 的构成。4.3 实时订阅排序参数随订阅一并下发当使用 Live Provider 时useList挂载后会调用liveProvider的subscribe方法channel 形如resources/${resource?.name}并把sorters等参数一并传入paramsuseResourceSubscription({ resource: identifier, types: [*], params: { meta: combinedMeta, pagination: prefferedPagination, hasPagination: isServerPagination, sorters: prefferedSorters, filters: prefferedFilters, subscriptionType: useList, ...liveParams, }, channel: resources/${resource?.name}, ... });这意味着实时场景下订阅上下文同样携带当前的排序条件相关实时事件*类型可以按需触发数据更新liveMode: auto | manual。4.4 返回值结构useList返回 TanStack QueryuseQuery的全部返回值并额外整理出result与overtimereturn { query: queryResponse, // QueryObserverResult result: { ...queryResponse?.data, data: queryResponse?.data?.data || EMPTY_ARRAY, // 默认空数组避免 undefined 报错 total: queryResponse?.data?.total, }, overtime: { elapsedTime }, };这也是示例代码中const products result.data ?? [];与query.isLoading、query.isError并行使用的类型依据。五、dataProvider 端以 simple-rest 的generateSort为例sorters到达getList后如何变成真实的 API 查询参数取决于数据提供方。以仓库内置的refinedev/simple-rest为例其getList实现位于 packages/simple-rest/src/provider.ts排序由工具函数 generateSort.ts 处理import type { CrudSorting } from refinedev/core; export const generateSort (sorters?: CrudSorting) { if (sorters sorters.length 0) { const _sort: string[] []; const _order: string[] []; sorters.map((item) { _sort.push(item.field); _order.push(item.order); }); return { _sort, _order, }; } return; };可以看到simple-rest 采用_sort与_order两个并列参数传递排序信息_sort收集所有field_order收集对应的order最终形成形如?_sortname_orderasc的查询串。因此多字段排序天然支持传入多个CrudSort对象即可_sort与_order会按索引一一对应无排序条件时返回undefinedprovider.ts中会跳过该参数的拼装不会向 API 发送多余的排序参数。如果你使用的是 GraphQL 数据提供方如refinedev/graphql、refinedev/hasura等sorters则会被映射为 GraphQL 的order_by/sort等查询变量。这印证了文档中的结论sorters语义统一落地方式由 dataProvider 自行决定。关于自定义 dataProvider 中getList的完整契约可参考 文档 data 部分 中创建数据提供方的教程。六、与分页、过滤协同使用排序通常与分页、过滤同时出现。文档中的分页示例展示了三者的协作模式参见 分页 Live Previewconst [currentPage, setCurrentPage] useState(1); const [pageSize, setPageSize] useState(5); const { result, query } useListIProduct, HttpError({ resource: products, pagination: { currentPage, pageSize, }, });在pagination中currentPage默认1、pageSize默认10与modeserver | client | off默认server共同控制分页行为。其中mode: client时useList会在客户端对getList返回的完整数据做切片见 useList.ts 中memoizedSelect的实现mode: off则完全关闭分页。过滤则通过filters属性传入配合CrudFilter类型与丰富的操作符eq、contains、startswith、between、or/and组合等完整清单见 types.tsfilters: [ { field: title, operator: contains, value: Foo, }, ],实际开发中一个典型组合如下——排序、过滤、分页三者并存任一状态变化都会因 query key 变化而自动触发重新请求useListIProduct, HttpError({ resource: products, sorters: [{ field: name, order }], filters: [{ field: material, operator: eq, value: steel }], pagination: { currentPage, pageSize }, });七、useList其余常用属性速览除排序外useList还提供以下常用属性完整定义见 useList 文档 与 useList.ts属性作用resource必填传给getList的资源标识通常对应 API 路径多资源同名时可改用identifier匹配dataProviderName配置了多个 dataProvider 时指定使用哪一个filters过滤条件数组CrudFilter[]透传给getListsorters排序条件数组CrudSort[]透传给getListpagination分页配置currentPage/pageSize/modequeryOptions透传给底层useQuery的选项如retry、enabled、select等meta附加元数据可用于自定义 dataProvider 行为或生成 GraphQL 查询successNotification/errorNotification自定义成功 / 失败通知需配置 NotificationProviderliveMode/onLiveEvent/liveParams实时订阅相关需配置 LiveProviderovertimeOptions请求超时监测配合返回的overtime.elapsedTime展示加载过久提示其中overtimeOptions的典型用法来自文档const { overtime } useList({ resource: products, overtimeOptions: { interval: 1000, onInterval(elapsedInterval) { console.log(elapsedInterval); }, }, }); // overtime.elapsedTime 依次为 undefined, 1000, 2000, 3000 ... { overtime.elapsedTime 4000 divthis takes a bit longer than expected/div; }八、小结围绕useList的排序能力本文可以提炼出以下关键结论使用sorters数组描述排序每个元素是{ field, order }类型为CrudSort支持多字段、可动态变更动态变更即自动刷新sorters参与查询键生成见 useList.ts 的queryKey构造变化会触发新请求无需手动调用refetch透传而非解释useList只负责把sorters原样交给dataProvider.getListREST 提供方如 simple-rest 的generateSort与 GraphQL 提供方各自决定如何映射为查询参数实时能力内置启用 Live Provider 后排序条件随订阅参数下发配合liveMode可实现实时更新列表类型安全贯穿始终SortOrder、CrudSort、CrudSorting定义于 packages/core/src/contexts/data/types.ts配合useListIProduct, HttpError的泛型参数可以构建出编译期即有保障的列表逻辑。参照文档中的 Live Preview 示例排序示例、基础用法、分页示例并对照源码 useList.ts 与 generateSort.ts你就可以在真实项目中快速落地一个支持动态排序、过滤与分页的数据列表页。【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考