refine v3 中 useTable 搜索表单实战:基于 onSearch 与 searchFormProps 实现 Ant Design 表格自定义搜索
2026/9/14 17:32:36 网站建设 项目流程

refine v3 中 useTable 搜索表单实战:基于 onSearch 与 searchFormProps 实现 Ant Design 表格自定义搜索

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

导读

本篇技术指南围绕 refine 项目(v3 分支)Ant Design 集成中useTableSearch(自定义搜索表单)能力展开,以官方文档中的实时预览代码块 _partial-use-table-search-live-preview.md 为骨架,结合源码与可运行示例深入讲解onSearchsearchFormProps的完整用法。读完本文,你将掌握:如何在 refine + Ant Design 项目中搭建一个与表格联动、支持任意查询条件的搜索表单,理解搜索提交后过滤条件如何转化为CrudFilters并驱动数据请求,以及如何与分页、syncWithLocation、初始/永久过滤等机制协同工作。

一、Search 功能概述:为什么需要自定义搜索表单

在 refine 的useTable中,基础用法、排序(Sorting)、筛选(Filtering)等能力开箱即用,但当你需要一个独立于表格列头的、自由组合的搜索区域时(例如在表格上方放置一个"按标题搜索"的输入框),就需要使用 Search 特性。

正如 useTable.md 文档所述:

We can useonSearchandsearchFormPropsproperties to make custom filter form.onSearchis a function that is called when the form is submitted.searchFormPropsis a property that is passed to the<Form>component.

即:

  • onSearch:表单提交时被调用的函数,接收表单的值,返回CrudFilters(或返回该类型的 Promise);
  • searchFormProps:需要透传给 Ant Design<Form>组件的属性,让表单与useTable的搜索逻辑绑定。

useTable本身是 Ant Design 集成层对核心包@pankod/refine-coreuseTable的扩展,底层通过useList拉取数据,因此搜索、排序、分页、筛选的过滤条件最终都会统一收敛到数据请求中。

二、完整示例:在文章列表上方实现"按标题搜索"

官方实时预览代码块演示了最典型的使用场景——在posts列表页顶部放置一个内联搜索表单,输入标题后提交,表格即按标题进行模糊匹配过滤。完整代码如下(取自关联文档,可直接运行):

import { IResourceComponentsProps, HttpError } from "@pankod/refine-core"; import { List, Table, TagField, useTable, // highlight-start Form, SaveButton, Input, // highlight-end } from "@pankod/refine-antd"; interface IPost { id: number; title: string; content: string; status: "published" | "draft" | "rejected"; } interface ISearch { title: string; } const PostList: React.FC<IResourceComponentsProps> = () => { const { tableProps, searchFormProps } = useTable<IPost, HttpError, ISearch>( { // highlight-start onSearch: (values) => { return [ { field: "title", operator: "contains", value: values.title, }, ]; }, // highlight-end }, ); return ( <List> {/* highlight-start */} <Form {...searchFormProps} layout="inline"> <Form.Item name="title"> <Input placeholder="Search by title" /> </Form.Item> <SaveButton onClick={searchFormProps.form?.submit} /> </Form> {/* highlight-end */} <Table {...tableProps} rowKey="id"> <Table.Column dataIndex="id" title="ID" /> <Table.Column dataIndex="title" title="Title" /> <Table.Column dataIndex="content" title="Content" /> <Table.Column dataIndex="status" title="Status" render={(value: string) => <TagField value={value} />} /> </Table> </List> ); };

2.1 逐段拆解

  1. 泛型三参数useTable<IPost, HttpError, ISearch>中第三个泛型ISearch用于声明搜索表单的字段结构(此处为{ title: string }),这也是useTable的三个类型参数之一(TSearchVariables,默认值为{})。
  2. onSearch返回过滤条件:提交时拿到values,返回一个包含单个CrudFilter的数组{ field: "title", operator: "contains", value: values.title },语义是"标题字段包含输入内容"。这里返回的CrudFilters会替换/合并进当前过滤状态,并随下一次数据请求发送给数据提供者。
  3. 表单绑定<Form {...searchFormProps} layout="inline">useTable内部的表单实例与 Ant Design<Form>打通;<Form.Item name="title">name必须与ISearch字段、onSearch读取的键保持一致。
  4. 手动触发提交<SaveButton onClick={searchFormProps.form?.submit} />通过表单实例的submit()主动提交,从而触发onSearchSaveButton@pankod/refine-antd提供的按钮组件,也可以替换为普通<Button htmlType="submit">

2.2 关于实时预览代码块的说明

关联文档本身是 Docusaurus 文档中的 live-preview 片段(url=http://localhost:3000/postspreviewHeight=420px),除上述主体代码外还包含预览环境相关代码:

setInitialRoutes(["/posts"]); // visible-block-start // ...上面展示的 PostList 组件代码... // visible-block-end setRefineProps({ resources: [ { name: "posts", list: PostList, }, ], }); render(<RefineAntdDemo />);

其中setInitialRoutes(["/posts"])将初始路由指向/posts(与预览地址localhost:3000/posts一致),setRefineProps注册posts资源并把PostList作为其list页面,render(<RefineAntdDemo />)挂载演示应用。在实际项目代码中,你只需要关注visible-block-startvisible-block-end之间的PostList组件即可。

三、深入源码:搜索提交背后的调用链

要真正理解 Search 特性,需要看useTable的 Ant Design 集成实现。核心文件是 packages/antd/src/hooks/table/useTable/useTable.ts。

3.1 类型定义

该 Hook 扩展了核心useTable的入参与返回值:

export type useTableProps<TQueryFnData, TError, TSearchVariables, TData> = useTablePropsCore<TQueryFnData, TError, TData> & { onSearch?: (data: TSearchVariables) => CrudFilters | Promise<CrudFilters>; }; export type useTableReturnType<TData, TError, TSearchVariables> = useTableCoreReturnType<TData, TError> & { searchFormProps: FormProps<TSearchVariables>; tableProps: TableProps<TData>; };

可以看到onSearch的签名正是"接收表单值 → 返回CrudFilters | Promise<CrudFilters>",支持异步处理(例如先请求某个接口再构造过滤条件)。

3.2 onFinish 的实现

searchFormProps的关键在于它被注入了自定义的onFinish(useTable.ts):

const onFinish = async (value: TSearchVariables) => { if (onSearch) { const searchFilters = await onSearch(value); setFilters(searchFilters); if (isPaginationEnabled) { setCurrentPage?.(1); } } };

从源码可以确认两个重要的行为事实:

  1. 提交即过滤:表单提交后,onSearch(value)的返回值被直接传入setFilters(searchFilters),从而更新useTable的过滤状态并触发重新请求。文档中"onSearchwill set the current page to 1"(将当前页重置为 1)在源码中也有明确对应:setCurrentPage?.(1),这保证了搜索后总是从第一页开始展示结果。
  2. 返回值被透传searchFormProps是由内部表单实例的formSF.formProps展开并覆盖onFinish后得到的:
return { searchFormProps: { ...formSF.formProps, onFinish, }, // ... };

此外,内部通过Form.useForm<TSearchVariables>()创建表单实例,因此你可以通过searchFormProps.form?.submit()以命令式方式提交表单,这正是示例中SaveButton的用法。

3.3 setFilters 与过滤行为

setFilters来自核心包 packages/core/src/hooks/useTable/index.ts,其签名支持两种调用方式:

setFilters: ((filters: CrudFilter[], behavior?: SetFilterBehavior) => void) & ((setter: (previousFilters: CrudFilter[]) => CrudFilter[]) => void);

其中SetFilterBehavior = "merge" | "replace",默认行为是merge(可在useTabledefaultSetFilterBehavior属性中改为"replace",也可在调用setFilters时通过第二参数临时覆盖):

  • merge(默认):新过滤条件与现有过滤条件合并——若新条件与已有条件同字段,则以新条件替换该字段的旧条件;若字段不同,则追加到现有条件中。
  • replace:用新过滤条件整体替换所有现有过滤条件。

对搜索场景而言,默认的merge意味着:搜索产生的标题过滤会与列头筛选、permanentFilter等并存,互不覆盖;而如果你希望搜索条件"一锤定音"地覆盖其他过滤,则可显式指定replace行为。

四、CrudFilters 与操作符:搜索条件的"标准语"

onSearch的返回值类型CrudFilters本质是CrudFilter[],每个CrudFilterfieldoperatorvalue三部分组成,定义见 interfaces.md:

KeyType说明
fieldstring过滤的字段名
operatorCrudOperators过滤操作符
valueany过滤值(可为空)

CrudOperators覆盖了常见的比较与字符串匹配语义,包括:

"eq" | "ne" | "lt" | "gt" | "lte" | "gte" | "in" | "nin" | "contains" | "ncontains" | "containss" | "ncontainss" | "between" | "nbetween" | "null" | "nnull" | "startswith" | "nstartswith" | "startswiths" | "nstartswiths" | "endswith" | "nendswith" | "endswiths" | "nendswiths";

常用操作符速查:

操作符含义
"eq"等于
"ne"不等于
"in"在数组中
"nin"不在数组中
"contains"包含(不区分大小写)
"containss"包含(区分大小写)
"between"介于两个值之间
"null"为空
"nnull"不为空
"startswith"以指定字符串开头
"endswith"以指定字符串结尾

示例中的operator: "contains"即"标题包含输入值"的模糊搜索。如果希望搜索区分大小写,可改用"containss"。这些操作符最终会由数据提供者(如 REST 提供者)转换为对应的查询参数,因此请以你所使用的数据提供者对操作符的实际支持为准。

五、更完整的搜索与过滤组合:借鉴官方示例项目

官方示例 examples/table-antd-use-table 提供了比 live-preview 更完整的列表页实践,可与 Search 特性互补理解。它展示了:

  • syncWithLocation: true:将分页、排序、过滤状态同步到 URL 查询参数,便于分享与书签;
  • filters.initial初始过滤:例如{ field: "title", operator: "contains", value: "" }{ field: "status", operator: "eq", value: "draft" }{ field: "category.id", operator: "in", value: [1, 2] }
  • sorters.initial初始排序:如按title升序;
  • 列头过滤:通过FilterDropdown组件包裹InputRadio.Group、多选Select实现按列过滤,并用getDefaultFilter/getDefaultSortOrder(定义于 packages/antd/src/definitions/table/index.ts)将当前过滤、排序状态回填到列头。

需要说明的是:Search(表单搜索)与 Filtering(列头筛选)是两条独立但共享同一份filters状态的路径。列头筛选经由<Table>onChange回调中的mapAntdFilterToCrudFilter(同样位于 packages/antd/src/definitions/table/index.ts)转换为CrudFilters;而表单搜索则经由onSearchsetFilters进入同一状态。二者都遵循merge/replace行为规则。

此外,示例中还使用了initialFilter/permanentFilterinitialSorter/permanentSorter的概念——initial*是"临时的",用户一旦手动变更即被清除;permanent*是"永久的",不可被用户操作清除。在配置搜索相关过滤时,可根据业务需要选择合适的一类。

六、Search 与 syncWithLocation 的协同

当启用syncWithLocation时,搜索表单与 URL 之间存在双向同步。在 useTable.ts 中有如下逻辑:

React.useEffect(() => { if (shouldSyncWithLocation) { // get registered fields of form const registeredFields = formSF.form.getFieldsValue() as Record<string, any>; // map `filters` for registered fields const filterFilterMap = Object.keys(registeredFields).reduce( (acc, curr) => { const filter = filters.find( (filter) => "field" in filter && filter.field === curr, ); if (filter) { acc[curr] = filter?.value; } return acc; }, {} as Record<string, any>, ); // set values to form formSF.form.setFieldsValue(filterFilterMap as any); } }, [shouldSyncWithLocation]);

也就是说:当 URL 查询参数中包含与表单字段同名的过滤条件时,useTable会把这些值回填到搜索表单中。这样用户通过 URL 打开页面时,搜索框会自动带上上一次的搜索内容,实现可分享、可书签的"搜索视图"。

对应的测试用例位于 packages/antd/src/hooks/table/useTable/useTable.spec.tsx,其中 "should pass form values to search form from params (syncWithLocation)" 用例验证了"从 URL 参数回填表单值"这一行为,是 Search +syncWithLocation协同工作的直接测试证据。

七、实战要点与注意事项

  1. Form.Itemname必须与onSearch读取的字段一致:例如name="title"对应values.title,否则拿到的值将是undefined
  2. 提交方式:既可以使用<SaveButton onClick={searchFormProps.form?.submit} />命令式提交,也可以在<Form>内放置<Button htmlType="submit">利用表单原生提交;两种方式都会触发onSearch
  3. 搜索后分页重置onSearch触发后当前页会自动重置为 1(源码中setCurrentPage?.(1)),因此无需手动处理"搜索后停留在第 5 页"的问题。
  4. 异步搜索onSearch支持返回Promise<CrudFilters>,可在其中先调用接口(如根据关键词预查询关联 ID)再构造过滤条件。
  5. 与列头筛选的关系:搜索表单与列头筛选共享过滤状态,默认merge行为下它们会叠加生效;若需互斥,可把defaultSetFilterBehavior设为"replace"
  6. 类型安全:善用第三个泛型参数TSearchVariables约束表单数据结构,searchFormProps会以FormProps<TSearchVariables>形式提供类型提示。
  7. 底层数据获取useTable底层使用useList拉取数据(参见 useList 文档),搜索产生的过滤条件会随getList请求一并发送给数据提供者,因此搜索能力本身不依赖任何 UI 框架,Ant Design 集成只是提供了便捷的表单封装。

八、关联资源索引

  • 本文骨架来源:_partial-use-table-search-live-preview.md(Search 实时预览代码块)
  • 父级 API 文档:useTable.md(含onSearchsearchFormProps完整属性说明)
  • 实现源码:packages/antd/src/hooks/table/useTable/useTable.ts(onFinishsearchFormPropsonChange实现)
  • 核心 Hook:packages/core/src/hooks/useTable/index.ts(setFilters、过滤行为、syncWithLocation
  • 类型与操作符:interfaces.md(CrudFiltersCrudOperators
  • 过滤转换工具:packages/antd/src/definitions/table/index.ts(mapAntdFilterToCrudFiltergetDefaultFiltergetDefaultSortOrder
  • 可运行示例:examples/table-antd-use-table/src/pages/posts/list.tsx(syncWithLocation、初始/永久过滤、列头筛选综合实践)
  • 相关测试:packages/antd/src/hooks/table/useTable/useTable.spec.tsx(syncWithLocation表单回填行为验证)

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

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询