Refine Table Search 实战:用 useTable 的 searchFormProps 与 onSearch 实现列表页搜索过滤
【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards & B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine
本文基于 Refine 官方进阶教程文档(Table Search),完整讲解如何在列表页利用useTableHook 构建搜索表单、通过onSearch将表单值转换为CrudFilters,并结合开源仓库中的 Hook 源码,剖析「表单提交 → 过滤条件生成 → 查询刷新」的完整调用链与类型约束,帮助你在 Ant Design 项目(如@pankod/refine-antd/@refinedev/antd)中快速落地可扩展的表格搜索/过滤功能。
整体思路:searchFormProps + onSearch
在 Refine 中,列表页的复杂搜索与过滤统一由useTableHook 驱动。其核心设计分为两步(对应官方文档 table-search.md 的主线):
- 构建搜索表单:从
useTable解构出searchFormProps,将其展开到 antd<Form>上,表单字段的name即为搜索变量名; - 转换过滤条件:通过
onSearch回调接收表单提交的值,返回一个CrudFilters对象,Refine 会据此重新发起useList查询。
这种「表单值 → 过滤器数组」的解耦方式意味着:表单 UI 想怎么设计都可以(输入框、日期范围、下拉框……),最终都收敛为统一、可被任意 dataProvider 理解的CrudFilters结构,搜索条件与数据层彻底分离。
第一步:用 searchFormProps 创建搜索表单
按照文档给出的示例,在列表页pages/list.tsx中引入Form、Table、useTable等组件,并从useTable<IPost>()中取出searchFormProps:
import { // highlight-start Form, Table, useTable, // highlight-end Row, Col, Icons, List, Button, DatePicker, Space, Input, } from "@pankod/refine-antd"; const { RangePicker } = DatePicker; export const ListPage: React.FC = () => { // highlight-next-line const { searchFormProps } = useTable<IPost>(); return ( // highlight-start <Row gutter={[16, 16]}> <Col lg={6} xs={24}> <Form layout="vertical" {...searchFormProps}> <Form.Item label="Search" name="q"> <Input placeholder="ID, Title, Content, etc." prefix={<Icons.SearchOutlined />} /> </Form.Item> <Form.Item label="Created At" name="createdAt"> <RangePicker /> </Form.Item> <Form.Item> <Button htmlType="submit" type="primary"> Filter </Button> </Form.Item> </Form> </Col> <Col lg={18} xs={24}> <List> <Table>...</Table> </List> </Col> </Row> // highlight-end ); }; interface IPost { id: number; title: string; createdAt: string; }要点说明:
<Form layout="vertical" {...searchFormProps}>:searchFormProps是一个标准的 antdFormProps(在源码中类型为FormProps<TSearchVariables>),直接展开即可让表单接管onFinish等生命周期;Form.Item的name决定搜索变量:上例中name="q"与name="createdAt"会分别成为onSearch(params)参数里的q与createdAt字段;- 布局上推荐用
Row/Col把搜索表单放在左侧窄栏(lg={6} xs={24})、表格放在右侧宽栏(lg={18} xs={24}),在小屏下自动堆叠为全宽。
第二步:onSearch 将表单值转换为 CrudFilters
文档强调:当表单提交后,onSearch方法会被执行,并拿到搜索表单的值;你需要为该回调返回一个CrudFilters类型的对象。完整示例(注意这里为useTable提供了第三个泛型参数,用于声明搜索变量类型):
... import { HttpError } from "@pankod/refine-core"; import { Dayjs } from "dayjs"; const { searchFormProps } = useTable< IPost, HttpError, { title: string; createdAt: [Dayjs, Dayjs] } >({ onSearch: (params) => { const filters: CrudFilters = []; const { q, createdAt } = params; filters.push( { field: "q", operator: "eq", value: q, }, { field: "createdAt", operator: "gte", value: createdAt ? createdAt[0].toISOString() : undefined, }, { field: "createdAt", operator: "lte", value: createdAt ? createdAt[1].toISOString() : undefined, }, ); return filters; }, }); ...文档中特别以 caution 提示:CrudFilters中的每个对象包含field、operator、value三个属性,它们共同描述「在哪个字段上、用什么操作符、以什么值进行过滤」。
从源码看:onSearch 之后发生了什么
结合仓库中 antd 适配包的 Hook 实现(useTable.ts)可以看到整条链路:
- 类型契约:
useTableProps在 core 返回类型之上扩展了onSearch?: (data: TSearchVariables) => CrudFilters | Promise<CrudFilters>(见 useTable.ts),因此onSearch既支持同步返回,也支持async写法(例如先从接口取选项列表再构造过滤器); - 表单接管:Hook 内部通过 antd 的
Form.useForm<TSearchVariables>()创建表单实例,并返回searchFormProps: { ...formSF.formProps, onFinish }(见 useTable.ts)——这正是第一步中<Form>展开的属性的来源; - 提交触发过滤与重置分页:内部
onFinish在表单提交时执行const searchFilters = await onSearch(value); setFilters(searchFilters);,并在分页开启时调用setCurrentPage?.(1)(见 useTable.ts)。也就是说:每次搜索都会把filters状态替换为你返回的条件,并把表格重置回第 1 页,随后 core 层的tableQuery依据新的filters重新请求数据; - URL 同步:Hook 中还包含
syncWithLocation相关逻辑——开启同步后,会读取表单中已注册的字段名,从当前filters中找到同名字段并把值回填到表单(见 useTable.ts)。从源码结构看,这意味着搜索条件可随 URL 分享/刷新还原,是搭建「可分享的筛选视图」的基础能力。
CrudFilters 结构详解:字段、操作符与值
CrudFilters的精确定义在 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:文档示例中使用的常规形式,field+operator+value三元组;ConditionalFilter:用operator: "or" | "and"组合一组子过滤器,可嵌套,用于表达「满足任一条件」等复杂逻辑。
CrudOperators支持的完整操作符列表(同样定义于 types.ts):
| 操作符 | 含义 |
|---|---|
eq/ne | 等于 / 不等于 |
eqs/nes | 等于 / 不等于(区分大小写) |
lt/gt/lte/gte | 小于 / 大于 / 小于等于 / 大于等于 |
in/nin | 在数组内 / 不在数组内 |
ina/nina | 部分元素在数组内 / 不在数组内 |
contains/ncontains | 包含 / 不包含 |
containss/ncontainss | 包含 / 不包含(区分大小写) |
between/nbetween | 区间内 / 区间外 |
null/nnull | 为空 / 不为空 |
startswith/nstartswith(含s大小写敏感变体) | 前缀匹配及其否定 |
endswith/nendswith(含s大小写敏感变体) | 后缀匹配及其否定 |
or/and | 逻辑组合(仅用于ConditionalFilter) |
文档示例中的日期范围搜索正是「一个字段两个条件」的典型写法:createdAt分别用gte(大于等于起始时间)与lte(小于等于结束时间)约束,RangePicker返回的[Dayjs, Dayjs]通过toISOString()转成标准时间字符串,未选择时传undefined由 dataProvider 忽略。如果后端支持更紧凑的写法,也可以用between操作符把两个边界合并为一个过滤器——具体以你的 dataProvider 文档为准。
版本适用性说明
需要留意:本教程文档面向 Refine v3(@pankod/refine-antd/@pankod/refine-core包命名),而当前仓库主干的 antd 适配包已从@pankod/refine-antd迁移为@refinedev/antd、核心逻辑迁移至@refinedev/core(见 useTable.ts 中的 import 来源)。searchFormProps+onSearch返回CrudFilters的使用模式在两个版本中一致,迁移时主要替换包名与个别类型导入路径即可,搜索表单的写法不受影响。
参考示例:table-antd-table-filter
文档末尾的 Example(CodeSandboxtable-antd-table-filter)对应仓库中的完整可运行项目 examples/table-antd-table-filter。其列表页 list.tsx 展示了生产级用法:
const { tableProps, searchFormProps } = useTable<...>({ onSearch: (params) => { // ...将 params 转换为 CrudFilters }, }); // ... <Filter formProps={searchFormProps} />可以看到它把searchFormProps作为formProps传给了一个可复用的Filter封装组件,与<Table {...tableProps} />搭配,即「搜索表单组件 + 表格组件」的组合模式。建议在本地安装依赖后运行该示例,直观体验提交搜索表单后表格数据、分页与 URL 的联动效果。
小结
- 搜索表单由
searchFormProps驱动,Form.Item的name即搜索变量名,表单 UI 与过滤逻辑彻底解耦; onSearch是唯一的「翻译层」:把任意形态的表单值转换为CrudFilters,返回后 Refine 自动重置到第 1 页并重新查询;CrudFilters的field/operator/value三元组覆盖等值、区间、包含、前缀、空值及or/and组合等绝大多数查询场景,且可被任意 dataProvider(REST、GraphQL、Supabase 等)统一消费;- 结合
syncWithLocation,搜索条件还能同步到 URL,实现可分享、可刷新还原的筛选视图。
【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards & B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考