news 2026/9/12 2:38:36

使用 Refine useList 实现资源列表过滤:filters 参数深度实战指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
使用 Refine useList 实现资源列表过滤:filters 参数深度实战指南

使用 Refine useList 实现资源列表过滤:filters 参数深度实战指南

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

导读:本文围绕 Refine v5 的useListHook 及其filters参数展开,讲解如何在列表数据获取中实现按字段、运算符的动态过滤,并结合packages/core的源码实现、类型定义与测试用例,说明过滤器如何从组件一路传递到 data provider 的getList方法,帮助读者掌握可控、可复现的过滤方案。

一、useList与过滤功能概述

在 Refine 中,useList是 TanStack Query 的useQuery的扩展版本,在继承其全部能力(缓存、重试、失效等)的基础上,增加了面向资源列表的语义化参数。当需要按照排序(sorters)、过滤(filters)、分页(pagination)等条件从resource获取数据时,即可使用该 Hook。

它的核心工作方式可以总结为两点(见 index.md):

  • 使用dataProvidergetList方法作为查询函数(query function);
  • 使用由传入属性生成的query key缓存数据,可在 TanStack Query Devtools 中查看。

过滤功能对应useListfilters属性。动态改变filters会触发新的请求——这正是本篇文章要展开的主题。仓库中对应功能的实时示例位于 _filtering-live-preview.md,本文将以该示例为主体进行讲解。

二、一个完整的动态过滤示例

以下代码来自 _filtering-live-preview.md,它演示了「根据下拉框选择,实时过滤商品列表」的完整场景:

import { useState } from "react"; import { useList, HttpError } from "@refinedev/core"; interface IProduct { id: number; name: string; material: string; } const ProductList: React.FC = () => { const [value, setValue] = useState("Cotton"); const { result, query } = useList<IProduct, HttpError>({ resource: "products", filters: [ { field: "material", operator: "eq", value, }, ], }); const products = result.data ?? []; if (query.isLoading) { return <div>Loading...</div>; } if (query.isError) { return <div>Something went wrong!</div>; } return ( <div> <span> material: </span> <select value={value} onChange={(e) => setValue(e.target.value)}> {["Cotton", "Bronze", "Plastic"].map((material) => ( <option key={material} value={material}> {material} </option> ))} </select> <ul> {products.map((product) => ( <li key={product.id}> <h4> {product.name} - ({product.material}) </h4> </li> ))} </ul> </div> ); };

示例同时配置了路由与资源(完整运行需要这部分配置):

setInitialRoutes(["/products"]); setRefineProps({ resources: [ { name: "products", list: "/products", }, ], }); render( <ReactRouter.BrowserRouter> <RefineHeadlessDemo> <ReactRouter.Routes> <ReactRouter.Route path="/products" element={<ProductList />} /> </ReactRouter.Routes> </RefineHeadlessDemo> </ReactRouter.BrowserRouter>, );

这个例子的关键点在于:

  1. value通过useState管理,与<select>双向绑定;
  2. filters数组中的value直接引用 React 状态;
  3. 下拉框切换时setValue改变状态,filters随之变化,useList会携带新的过滤条件重新请求;
  4. 通过query.isLoadingquery.isError分别处理加载中和出错状态;
  5. 使用result.data(类型为IProduct[])渲染列表,其内部数据来自query的响应。

需要注意:这里的operator: "eq"表示精确相等匹配,这是 Refine 中大量内置运算符之一(完整运算符见下文)。

三、filters参数的结构与运算符体系

filters会被原样传递给getList方法(index.md 中 Filtering 一节明确说明),用于向 API 发送过滤查询参数。其基本结构是一个过滤条件对象数组:

useList({ filters: [ { field: "title", operator: "contains", value: "Foo", }, ], });

3.1 类型定义:LogicalFilter 与 ConditionalFilter

在 packages/core/src/contexts/data/types.ts 中,过滤条件被划分为两类:

export type LogicalFilter = { field: string; operator: Exclude<CrudOperators, "or" | "and">; value: any; }; export type ConditionalFilter = { key?: string; operator: Extract<CrudOperators, "or" | "and">; value: (LogicalFilter | ConditionalFilter)[]; }; export type CrudFilter = LogicalFilter | ConditionalFilter; export type CrudFilters = CrudFilter[];
  • LogicalFilter(逻辑过滤):单字段、单运算符的条件,是绝大多数场景下的用法;
  • ConditionalFilter(条件组合):通过or/and运算符嵌套组合多个子条件,用于表达复杂查询;
  • CrudFiltersCrudFilter[],也就是useListfilters参数类型。

3.2 完整的运算符列表

同一文件(types.ts)中定义了CrudOperators联合类型,涵盖以下运算符:

运算符含义
eq/ne等于 / 不等于
eqs/nes等于(大小写敏感)/ 不等于(大小写敏感)
lt/gt/lte/gte小于 / 大于 / 小于等于 / 大于等于
in/nin属于 / 不属于(数组)
ina/nina属于(大小写敏感)/ 不属于(大小写敏感)
contains/ncontains包含 / 不包含
containss/ncontainss包含(大小写敏感)/ 不包含(大小写敏感)
between/nbetween介于 / 不介于
null/nnull为空 / 不为空
startswith/nstartswith以…开头 / 不以…开头
startswiths/nstartswiths以…开头(大小写敏感)/ 不以…开头(大小写敏感)
endswith/nendswith以…结尾 / 不以…结尾
endswiths/nendswiths以…结尾(大小写敏感)/ 不以…结尾(大小写敏感)
or/and条件组合运算符(仅用于 ConditionalFilter)

这一整套运算符由 Refine 内置定义,具体某个运算符最终是否生效,取决于所选 data provider 对filters的解析与请求参数映射实现(例如@refinedev/simple-rest@refinedev/rest等包各自实现了将CrudFilters转换成查询字符串或请求体的逻辑)。

四、useList中过滤的底层实现链路

理解了参数结构后,我们来看filters在源码中的完整流转过程。核心实现位于 packages/core/src/hooks/data/useList.ts。

4.1 参数接收与资源解析

useList通过useResourceParams解析resource(支持直接传入名称或使用identifier匹配),并通过useDataProviderpickDataProvider选择实际使用的 data provider(useList.ts):

const { resources, resource, identifier } = useResourceParams({ resource: resourceFromProp, }); const dataProvider = useDataProvider(); // ... const pickedDataProvider = pickDataProvider(identifier, dataProviderName, resources); const prefferedFilters = filters; // ... const { getList } = dataProvider(pickedDataProvider);

过滤条件会被原样保留(prefferedFilters),并在查询函数中传递给getList

4.2 query key 与过滤联动

useList基于属性构建查询键,其中过滤条件直接影响 query key(useList.ts):

queryKey: keys() .data(pickedDataProvider) .resource(identifier ?? "") .action("list") .params({ ...(preferredMeta || {}), filters: prefferedFilters, ...(isServerPagination && { pagination: prefferedPagination }), ...(sorters && { sorters }), }) .get(),

这意味着:当filters中的字段、运算符或值发生变化时,query key 也随之变化,TanStack Query 会据此发起一次全新的请求。这也解释了为什么「动态改变filters会触发新请求」——这是过滤功能与 React 状态天然协同的根本原因。

4.3 请求发送:filters 传给 getList

查询函数(queryFn)中,filterspaginationsortersmeta一起被传给getList(useList.ts):

queryFn: (context) => { const meta = { ...combinedMeta, ...prepareQueryContext(context), }; return getList<TQueryFnData>({ resource: resource?.name ?? "", pagination: prefferedPagination, filters: prefferedFilters, sorters: prefferedSorters, meta, }); },

最终由具体 data provider 将过滤条件翻译成 API 可识别的参数(如 REST 查询字符串、GraphQL 查询条件等)。

4.4 返回值结构

useList返回的对象中(useList.ts):

  • query:TanStack Query 的QueryObserverResult,提供isLoadingisErrordata等状态;
  • result:解包后的数据,data为数组(无数据时返回冻结的空数组EMPTY_ARRAY),total为总行数;
  • overtime:请求超时信息(elapsedTime)。

五、复杂过滤:用or/and组合条件

当业务需要「A 或 B」这类条件时,可以嵌套ConditionalFilter。例如过滤出materialCottonPlastic的商品:

useList<IProduct, HttpError>({ resource: "products", filters: [ { operator: "or", value: [ { field: "material", operator: "eq", value: "Cotton" }, { field: "material", operator: "eq", value: "Plastic" }, ], }, ], });

从类型定义可以看出,ConditionalFiltervalue是递归结构,支持任意深度的嵌套组合。这类结构化过滤器同样会在 query key 中参与缓存标识,因此组合条件变化时也会自动重新请求。

六、测试用例对过滤行为的验证

仓库中的单元测试确认了filters会被原样传给getList。在 packages/core/src/hooks/data/useList.spec.tsx 中,测试用例断言了调用参数:

useList({ resource: "posts", filters: [{ field: "id", operator: "eq", value: 1 }], pagination: { mode: "client", currentPage: 10, pageSize: 5 }, sorters: [{ field: "id", order: "asc" }], }); // ... expect(getListMock).toHaveBeenCalledWith( expect.objectContaining({ filters: [{ field: "id", operator: "eq", value: 1 }], pagination: { mode: "client", currentPage: 10, pageSize: 5 }, sorters: [{ field: "id", order: "asc" }], }), );

该测试以「过滤条件被透传给 data provider 的 getList」为断言目标,从测试层面佐证了本文第 4 节描述的调用链。

七、常用配套参数速查

围绕过滤场景,useList还常与以下参数搭配使用(完整参数见 index.md):

参数说明示例
resource(必填)资源名,通常作为 API 端点路径resource: "categories"
dataProviderName多 data provider 时指定使用哪一个dataProviderName: "second-data-provider"
pagination分页参数:currentPagepageSizemode"off"/"client"/"server"pagination: { mode: "off" }
sorters排序参数数组sorters: [{ field: "title", order: "asc" }]
queryOptions透传给useQuery的额外选项(如retryenabledqueryOptions: { retry: 3 }
meta传给 data provider 的附加信息(如自定义 headers、GraphQL 查询构造)meta: { headers: { "x-meta-data": "true" } }
liveMode/onLiveEvent/liveParams实时订阅相关(需配置 Live Provider)liveMode: "auto"
overtimeOptions请求超时提示,返回overtime.elapsedTimeovertimeOptions: { interval: 1000, onInterval }

八、小结

Refine 的useList将列表过滤封装成了声明式的filters数组:开发者只需描述「过滤哪些字段、用什么运算符、匹配什么值」,即可获得缓存、加载态、错误态、实时订阅等一整套能力。从源码链路看,filters会被:

  1. 作为 query key 的一部分参与缓存标识;
  2. 在查询函数中原样传递给getList
  3. 由具体 data provider 翻译为 API 请求参数。

结合 React 状态(如示例中的<select>),filters的值变化会自动触发重新请求,从而实现开箱即用的动态过滤体验。若要深入定制,可进一步阅读 Refine 的 data provider 实现(如 packages/simple-rest)以了解CrudFilters到查询参数的具体映射逻辑。

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

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/12 2:37:21

Navicat Premium 17 数据库管理实战指南:升级、安装与高效运维

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/12 2:35:28

MQTT在云边端一体化中的通信实战与避坑指南

做物联网项目的这几年&#xff0c;我越来越确认一件事&#xff1a;云边端一体化听起来是个架构概念&#xff0c;但真正落地的时候&#xff0c;最先卡住你的往往不是算法、不是算力&#xff0c;而是设备、边缘网关和云端之间那根看不见的“通信神经”。设备上报的数据到不了边缘…

作者头像 李华
网站建设 2026/9/12 2:30:45

数据结构高频考点全解析:从链表到图的代码实战与复习指南

1. 数据结构到底在考什么&#xff1a;一张全景图帮你定优先级先说实话&#xff0c;数据结构这门课&#xff0c;大部分人在学的时候是懵的。学的时候觉得每个知识点都像一座孤岛&#xff0c;链表是链表、树是树、图是图&#xff0c;好像谁也挨不上谁。等到期末复习、考研冲刺或者…

作者头像 李华