使用 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):
- 使用
dataProvider的getList方法作为查询函数(query function); - 使用由传入属性生成的query key缓存数据,可在 TanStack Query Devtools 中查看。
过滤功能对应useList的filters属性。动态改变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>, );这个例子的关键点在于:
value通过useState管理,与<select>双向绑定;filters数组中的value直接引用 React 状态;- 下拉框切换时
setValue改变状态,filters随之变化,useList会携带新的过滤条件重新请求; - 通过
query.isLoading与query.isError分别处理加载中和出错状态; - 使用
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运算符嵌套组合多个子条件,用于表达复杂查询; CrudFilters即CrudFilter[],也就是useList的filters参数类型。
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匹配),并通过useDataProvider与pickDataProvider选择实际使用的 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)中,filters与pagination、sorters、meta一起被传给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,提供isLoading、isError、data等状态;result:解包后的数据,data为数组(无数据时返回冻结的空数组EMPTY_ARRAY),total为总行数;overtime:请求超时信息(elapsedTime)。
五、复杂过滤:用or/and组合条件
当业务需要「A 或 B」这类条件时,可以嵌套ConditionalFilter。例如过滤出material为Cotton或Plastic的商品:
useList<IProduct, HttpError>({ resource: "products", filters: [ { operator: "or", value: [ { field: "material", operator: "eq", value: "Cotton" }, { field: "material", operator: "eq", value: "Plastic" }, ], }, ], });从类型定义可以看出,ConditionalFilter的value是递归结构,支持任意深度的嵌套组合。这类结构化过滤器同样会在 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 | 分页参数:currentPage、pageSize、mode("off"/"client"/"server") | pagination: { mode: "off" } |
sorters | 排序参数数组 | sorters: [{ field: "title", order: "asc" }] |
queryOptions | 透传给useQuery的额外选项(如retry、enabled) | queryOptions: { retry: 3 } |
meta | 传给 data provider 的附加信息(如自定义 headers、GraphQL 查询构造) | meta: { headers: { "x-meta-data": "true" } } |
liveMode/onLiveEvent/liveParams | 实时订阅相关(需配置 Live Provider) | liveMode: "auto" |
overtimeOptions | 请求超时提示,返回overtime.elapsedTime | overtimeOptions: { interval: 1000, onInterval } |
八、小结
Refine 的useList将列表过滤封装成了声明式的filters数组:开发者只需描述「过滤哪些字段、用什么运算符、匹配什么值」,即可获得缓存、加载态、错误态、实时订阅等一整套能力。从源码链路看,filters会被:
- 作为 query key 的一部分参与缓存标识;
- 在查询函数中原样传递给
getList; - 由具体 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),仅供参考