Refine v5 使用指南:useTable Hook 与 Ant Design 表格的分页、排序、筛选实战
2026/9/13 20:30:15 网站建设 项目流程

Refine v5 使用指南:useTable Hook 与 Ant Design 表格的分页、排序、筛选实战

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

导读

useTable是 Refine 面向 Ant Design 生态提供的核心表格 Hook,它返回与 Ant Design<Table>为主线,结合@refinedev/antd@refinedev/core的实际源码,讲解useTable的完整用法、全部可配置属性、返回值语义,以及常见业务场景(关系数据展示、客户端筛选、客户端排序)的解决方案。读完本文,你将能够独立搭建一个支持服务端分页、排序、筛选与可分享 URL 状态的专业后台表格页面。

认识 useTable:基于 useList 的 Ant Design 表格适配层

useTable@refinedev/antd包提供的 Hook,通过它你可以在不手写任何数据请求逻辑的情况下,获得与 Ant Design<Table>组件兼容的全部属性。排序、筛选、分页等表格核心特性全部内置,无需额外配置。

从源码结构看,useTable是分层架构中的 UI 适配层:

  • 数据获取与状态管理核心在@refinedev/coreuseTable中实现,底层通过useList发起数据请求;
  • @refinedev/antduseTable将 core 层的返回值映射为 Ant Design<Table>所需的tableProps,并额外提供searchFormProps(搜索表单属性)与onSearch(搜索回调)。

这意味着:core 层useTable的所有特性(分页、排序、筛选、实时更新、超时检测等)在 Ant Design 版本中全部可用,同时你还能直接使用 Ant Design<Table>的全部既有功能。

在 packages/antd/src/hooks/table/useTable/useTable.ts 中可以看到,antd 版本的useTable首先调用useTableCore,然后在其返回值之上进行二次封装——例如用 Ant Design 的Form.useForm维护搜索表单状态、用Grid.useBreakpoint()根据响应式断点决定分页条的位置与形态。这正是“UI 适配层”一词的准确含义。

基础用法

在最基础的场景中,useTable会直接返回接口(endpoint)返回的数据,并默认从当前路由 URL 推断resource

import { useTable } from "@refinedev/antd"; import { Table } from "antd"; const { tableProps } = useTable<IPost>(); <Table {...tableProps} rowKey="id"> <Table.Column dataIndex="id" title="ID" /> <Table.Column dataIndex="title" title="Title" /> </Table>;

tableProps展开到<Table>上,数据源、加载状态、分页配置、排序筛选回调就全部接好了。对应的官方可运行示例位于 examples/table-antd-use-table,其端到端测试见 cypress/e2e/table-antd-use-table。

分页(Pagination)

分页能力由tableProps.pagination开箱即用地提供。它会为<Table>生成分页链接(而不是依赖 React 内部 state),并覆写<Table>默认的pagination.itemRender,让每个页码都渲染为基于路由的链接。

如果开启了syncWithLocation,分页状态还会与 URL 查询参数双向同步(详见后文)。

自定义分页条

当你需要调整<Table>的分页展示时,把tableProps.pagination对象回传给<Table>pagination属性,然后按需覆盖其中的字段:

const { tableProps } = useTable<IPost>(); <Table {...tableProps} rowKey="id" pagination={{ ...tableProps.pagination, position: ["bottomCenter"], size: "small", }} > {/* ... */} </Table>;

实现提示

  • 默认情况下分页在服务端完成:每次翻页都会用新的currentPagepageSize重新请求接口;
  • 若要在客户端完成分页,将pagination.mode设为"client",此时会一次性拉取全部记录再在前端分页;
  • 设置为"off"则完全禁用分页,拉取所有记录。

从 useTable.ts 的antdPagination()实现可以看到,分页对象会根据Grid.useBreakpoint()的结果自动调整:小屏设备使用simple模式并将分页条位置改为["bottomCenter"],大屏默认["bottomRight"]total取服务端返回的记录总数,用于计算总页数。分页链接由 paginationLink.tsx 中的PaginationLink组件渲染,它通过useLink()拿到当前路由方案(React Router / Next.js / Remix)对应的Link,从而保证与项目路由系统一致。

排序(Sorting)

要给某列开启排序,只需为对应的<Table.Column>添加sorter属性。开启syncWithLocation后,排序状态同样会与 URL 同步。

排序生效期间,API 请求中使用的字段名取自<Column>key属性;若列没有设置key,则回退使用dataIndex。当你的dataIndex与后端排序字段名不一致时,这个机制就派上了用场。

多列排序时,sorter属性必须提供multiple值,用来指定该列在排序中的优先级:

<Table.Column dataIndex="title" title="Title" sorter={{ multiple: 1 }} />

在 useTable.ts 的onChange回调中可以看到排序与筛选的处理链路:Ant Design 的SorterResult通过mapAntdSorterToCrudSorting映射为 Refine 的CrudSorting,再交给setSorters更新 core 层状态;随后 core 层会将其作为sorters参数传给useList,由数据提供者(data provider)拼接到实际请求中。映射函数定义在 packages/antd/src/definitions/table。

筛选(Filtering)

基于列值的筛选通过<Table.Column>组件内,并把filterDropdown回调收到的属性透传给该组件:

<Table.Column dataIndex="status" title="Status" filterDropdown={(props) => ( <FilterDropdown {...props}> <Radio.Group> <Radio value="published">Published</Radio> <Radio value="draft">Draft</Radio> <Radio value="rejected">Rejected</Radio> </Radio.Group> </FilterDropdown> )} />

onChange中,Ant Design 的列级筛选对象(Record<string, FilterValue | null>)通过mapAntdFilterToCrudFilter映射为 Refine 的CrudFilter[],进而触发setFilters。开启syncWithLocation后,筛选状态同样与 URL 双向同步。

初始排序与初始筛选(Initial Filter and Sorter)

使用sorters.initialfilters.initial设置初始状态时,务必同时为<Table.Column>添加getDefaultSortOrderdefaultFilteredValue,否则 Hook 的内部状态可能与表格展示不同步:

const { tableProps, sorters, filters } = useTable({ sorters: { initial: [ { field: "title", order: "asc", }, ], }, filters: { initial: [ { field: "status", operator: "eq", value: "published", }, ], }, }); // --- <Table.Column dataIndex="title" title="Title" defaultSortOrder={getDefaultSortOrder("title", sorters)} /> <Table.Column dataIndex="status" title="Status" render={(value) => <TagField value={value} />} defaultFilteredValue={getDefaultFilter("status", filters)} filterDropdown={(props) => ( <FilterDropdown {...props}> <Radio.Group> <Radio value="published">Published</Radio> <Radio value="draft">Draft</Radio> <Radio value="rejected">Rejected</Radio> </Radio.Group> </FilterDropdown> )} />

查找某个字段的筛选值:getDefaultFilter

Refine 提供getDefaultFilter函数(定义于 core 包的definitions/table相关文件中),用于从当前 filters 状态中取出指定字段的筛选值:

import { getDefaultFilter, useTable } from "@refinedev/antd"; const MyComponent = () => { const { filters } = useTable({ filters: { initial: [ { field: "name", operator: "contains", value: "John Doe", }, ], }, }); const nameFilterValue = getDefaultFilter("name", filters, "contains"); console.log(nameFilterValue); // "John Doe" return { /** ... */ }; };

getDefaultFilter的第三个参数operator是可选的,传入后可以精确匹配指定运算符的筛选条件。

自定义搜索表单(Search)

useTable提供onSearchsearchFormProps两个属性来构建自定义筛选表单:

  • onSearch:表单提交时被调用,接收表单值并返回CrudFilters | Promise<CrudFilters>;调用后会把当前页重置为第 1 页;
  • searchFormProps:需要传给 Ant Design<Form>组件的属性。
const { searchFormProps, tableProps } = useTable({ onSearch: (values) => { return [ { field: "title", operator: "contains", value: values.title, }, ]; }, }); <List> <Form {...searchFormProps} layout="inline"> <Form.Item name="title"> <Input placeholder="Search by title" /> </Form.Item> <SaveButton onClick={searchFormProps.form?.submit} /> </Form> <Table {...tableProps} rowKey="id"> <Table.Column title="Title" dataIndex="title" /> </Table> </List>;

从 useTable.ts 的onFinish实现可以看到,表单提交时onSearch返回的筛选条件会被setFilters接收,若分页开启则同时setCurrentPage(1)。另外,当开启syncWithLocation后,Hook 会在 effect 中读取表单已注册字段,把 URL 中对应的筛选值回填到表单(见 useTable.ts),实现“URL 即表单状态”的完整闭环。

实时更新(Realtime Updates)

该能力需要配置LiveProvider才能生效。

useTable挂载时,它会用channelresource等参数调用liveProvidersubscribe方法,从而订阅实时更新。配合liveMode: "auto",收到相关事件后数据会自动刷新;使用liveMode: "manual"则需手动处理。更多细节可参考liveProvider文档。

在 antd 适配层中还有一个值得注意的细节:tableProps.loadingliveMode === "auto"时取isLoading,否则取!isFetched(见 useTable.ts),这保证了实时模式下自动更新期间不会闪烁加载态。

全部可配置属性(Properties)详解

下表是useTable的完整配置项。所有属性都透传给 core 层useTable,antd 层仅在此基础上增加onSearch(见 useTable.ts 中的类型定义)。

resource

默认从路由推断resource,也可显式指定:

useTable({ resource: "categories", });

如果存在多个同名资源,可以传identifier代替name作为主匹配键;数据提供者的方法仍使用<Refine/>组件中定义的资源name。详见identifier说明。

onSearch

searchFormProps.onFinish被调用时触发,接收表单值,返回CrudFilters | Promise<CrudFilters>,并将当前页重置为 1。适合做任意条件的自定义查询筛选(示例见上文“自定义搜索表单”)。

dataProviderName

存在多个dataProvider时,用它指定当前资源使用哪一个:

useTable({ dataProviderName: "second-data-provider", });

pagination.currentPage

设置初始页码,默认1

useTable({ pagination: { currentPage: 2, }, });

pagination.pageSize

设置初始每页条数,默认10

useTable({ pagination: { pageSize: 20, }, });

pagination.mode

取值"off""server""client",默认"server"

  • "off":禁用分页,拉取全部记录;
  • "client":客户端分页,先拉取全部记录再在前端切分;
  • "server":服务端分页,用currentPagepageSize请求对应页数据。
useTable({ pagination: { mode: "client", }, });

sorters.initial

设置排序的初始值。initial不是持久的——用户一旦改变排序就会被清除。需要持久排序请用sorters.permanent。排序值类型参见CrudSorting接口:

useTable({ sorters: { initial: [ { field: "name", order: "asc", }, ], }, });

sorters.permanent

设置排序的持久值。permanent不可变——用户改变排序时不会被清除。需要临时排序请用sorters.initial

useTable({ sorters: { permanent: [ { field: "name", order: "asc", }, ], }, });

在 core 层实现中,permanent排序会被合并进最终传给useListsorters(通过unionSorters),而initial只作为useState的初始值,因此用户交互后initial会被覆盖(见 packages/core/src/hooks/useTable/index.ts)。

sorters.mode

取值"off""server",默认"server"

  • "off":排序值不发送给服务端,可在客户端用sorters自行排序;
  • "server":服务端排序,用sorters值请求数据。
useTable({ sorters: { mode: "server", }, });

filters.initial

设置筛选的初始值。initial不是持久的——用户一旦改变筛选就会被清除。需要持久筛选请用filters.permanent。筛选值类型参见CrudFilters接口:

useTable({ filters: { initial: [ { field: "name", operator: "contains", value: "Foo", }, ], }, });

filters.permanent

设置筛选的持久值。permanent不可变——用户改变筛选时不会被清除。需要临时筛选请用filters.initial

useTable({ filters: { permanent: [ { field: "name", operator: "contains", value: "Foo", }, ], }, });

filters.defaultBehavior

筛选行为取值"merge"(默认)或"replace"

  • "merge":新筛选与现有筛选合并——同一字段的新值替换旧值,不同字段则追加;
  • "replace":新筛选整体替换现有筛选——旧筛选全部移除,仅保留新筛选。

可通过setFilters的第二个参数临时覆盖默认行为:

useTable({ filters: { defaultBehavior: "replace", }, });

在 core 层 index.ts 中,setFilters被实现为三个分支:merge模式用unionFilters(preferredPermanentFilters, newFilters, prevFilters)合并;replace模式用unionFilters(preferredPermanentFilters, newFilters)替换;函数式调用则基于prevFilters计算。无论哪种模式,permanent筛选都会被unionFilters重新并入,这保证了持久筛选永远存在。

filters.mode

取值"off""server",默认"server"

  • "off":筛选值不发送给服务端,可在客户端用filters自行筛选;
  • "server":服务端筛选,用filters值请求数据。
useTable({ filters: { mode: "off", }, });

syncWithLocation

开启后,useTable的状态(排序、筛选、分页)会自动编码进 URL 查询参数;URL 变化时 Hook 状态也随之更新。这让表格状态可以跨路由/跨页面共享,用户还能为特定表格视图加书签或分享链接。默认false

useTable({ syncWithLocation: true, });

注意:syncWithLocation也可以在<Refine/>组件 上全局配置。antd 适配层会通过useSyncWithLocation()读取全局默认值,并以 props 传入值为准(见 useTable.ts)。

queryOptions

useTable通过useList获取数据,因此可以把queryOptions透传给它(如retrystaleTime等):

useTable({ queryOptions: { retry: 3, }, });

meta

meta用于向数据提供者方法传递附加信息,常见用途:

  • 针对特定用例定制数据提供者方法;
  • 用纯 JavaScript 对象(JSON)生成 GraphQL 查询。
useTable({ meta: { headers: { "x-meta-data": "true" }, }, }); const myDataProvider = { //... getList: async ({ resource, pagination, sorters, filters, meta, }) => { const headers = meta?.headers ?? {}; const url = `${apiUrl}/${resource}`; //... const { data, headers } = await httpClient.get(`${url}`, { headers }); return { data, }; }, //... };

meta 的通用概念详见 General Concepts 文档。

successNotification

需要配置NotificationProvider才能生效。

数据获取成功后,useTable会调用NotificationProvideropen方法弹出成功通知,可用该属性自定义:

useTable({ successNotification: (data, values, resource) => { return { message: `${data.title} Successfully fetched.`, description: "Success with no errors", type: "success", }; }, });

errorNotification

需要配置NotificationProvider才能生效。

数据获取失败时弹出错误通知,可用该属性自定义:

useTable({ errorNotification: (data, values, resource) => { return { message: `Something went wrong when getting ${data.id}`, description: "Error", type: "error", }; }, });

liveMode

需要配置LiveProvider才能生效。

决定收到相关实时事件后是否自动更新数据:"auto"自动更新,"manual"手动处理。详见 Live / Realtime 文档:

useTable({ liveMode: "auto", });

onLiveEvent

需要配置LiveProvider才能生效。

订阅到新事件时执行的回调函数:

useTable({ onLiveEvent: (event) => { console.log(event); }, });

liveParams

需要配置LiveProvider才能生效。

传递给liveProvider.subscribe方法的参数,详见 subscribe。

overtimeOptions

为请求启用加载超时检测,适合在请求耗时过长时展示加载提示:

  • interval:时间间隔(毫秒);
  • onInterval:每个间隔触发一次的函数。

Hook 返回的overtime.elapsedTime表示已耗时(毫秒),请求完成后变为undefined

const { overtime } = useTable({ //... overtimeOptions: { interval: 1000, onInterval(elapsedInterval) { console.log(elapsedInterval); }, }, }); console.log(overtime.elapsedTime); // undefined, 1000, 2000, 3000 4000, ... // You can use it like this: { elapsedTime >= 4000 && <div>this takes a bit longer than expected</div>; }

返回值(Return Values)详解

tableProps

传给 Ant Design<Table>组件的属性集合,包含:

  • onChange:用户对表格进行操作(筛选、排序等)时执行的回调。

    ⚠️ 注意:useTable正是通过该函数处理排序、筛选与分页的。如果你覆写onChange,就必须手动处理这些操作。

    const { tableProps } = useTable(); <Table {...tableProps} onChange={tableProps.onChange} rowKey="id"> <Table.Column title="Title" dataIndex="title" /> </Table>;
  • dataSource:表格展示的数据,即useList获取到的记录。

  • loading:数据是否正在获取。

  • pagination:分页配置值(pageSizecurrentPageposition等)。

  • scroll:表格是否可滚动,默认{ x: true }

searchFormProps

返回 Ant Design<Form>实例相关属性。当searchFormProps.onFinish被调用时会触发onSearch;也可用searchFormProps.form.submit手动提交表单。典型用法是构建表格上方的筛选表单:

import { HttpError } from "@refinedev/core"; import { List, useTable, SaveButton } from "@refinedev/antd"; import { Table, Form, Input } from "antd"; interface IPost { id: number; title: string; } interface ISearch { title: string; } const PostList: React.FC = () => { const { searchFormProps, tableProps } = useTable<IPost, HttpError, ISearch>({ onSearch: (values) => { return [ { field: "title", operator: "contains", value: values.title, }, ]; }, }); return ( <List> <Form {...searchFormProps} layout="inline"> <Form.Item name="title"> <Input placeholder="Search by title" /> </Form.Item> <SaveButton onClick={searchFormProps.form?.submit} /> </Form> <Table {...tableProps} rowKey="id"> <Table.Column dataIndex="id" title="ID" /> <Table.Column title="Title" dataIndex="title" /> </Table> </List> ); };

tableQuery

useList的返回值(react-queryuseQuery结果)。

sorters / setSorters

  • sorters:当前排序状态;
  • setSorters:设置排序状态的函数,类型为(sorters: CrudSorting) => void

filters / setFilters

  • filters:当前筛选状态;
  • setFilters:设置筛选状态的函数,支持两种调用方式:
((filters: CrudFilters, behavior?: SetFilterBehavior) => void) & ((setter: (prevFilters: CrudFilters) => CrudFilters) => void);

currentPage / setCurrentPage / pageSize / setPageSize

  • currentPage:当前页码状态;分页禁用时为undefined
  • setCurrentPageReact.Dispatch<React.SetStateAction<number>> | undefined
  • pageSize:当前每页条数;分页禁用时为undefined
  • setPageSizeReact.Dispatch<React.SetStateAction<number>> | undefined

pageCount

总页数状态;分页禁用时为undefined。在 core 层通过Math.ceil(total / pageSize)计算(见 index.ts)。

createLinkForSyncWithLocation

生成syncWithLocation可用链接的函数,接收SyncWithLocationParams并返回 URL 字符串:

(params: SyncWithLocationParams) => string;

overtime

返回{ elapsedTime?: number }elapsedTime为已耗时(毫秒),请求完成后为undefined

const { overtime } = useTable(); console.log(overtime.elapsedTime); // undefined, 1000, 2000, 3000 4000, ...

常见问题(FAQ)

如何处理关系数据?

使用useMany获取关系数据,并借助useSelect实现按分类(categories)筛选<Table>。典型的“博客文章 + 分类”列表页会先用useTable拿到文章,再从文章数据中提取分类 ID,通过useMany批量查询分类名称,最后用useSelect生成分类筛选下拉框。

如何做客户端筛选?

filters.mode设为"off"以禁用服务端筛选,此时useTable与 Ant Design<Table>自带的列筛选功能完全兼容:

import { useTable } from "@refinedev/antd"; import { Table } from "antd"; const ListPage = () => { const { tableProps } = useTable({ filters: { mode: "off", }, }); return ( <Table {...tableProps} rowKey="id"> {/* ... */} <Table.Column dataIndex="status" title="Status" filters={[ { text: "Published", value: "published", }, { text: "Draft", value: "draft", }, { text: "Rejected", value: "rejected", }, ]} onFilter={(value, record) => record.status === value} /> </Table> ); };

如何做客户端排序?

sorters.mode设为"off"以禁用服务端排序,此时可完全使用 Ant Design<Table>自带的列排序功能:

import { useTable } from "@refinedev/antd"; import { Table } from "antd"; const ListPage = () => { const { tableProps } = useTable({ sorters: { mode: "off", }, }); return ( <Table {...tableProps} rowKey="id"> <Table.Column dataIndex="id" title="ID" sorter={(a, b) => a.id - b.id} /> {/* ... */} </Table> ); };

API 速查

类型参数(Type Parameters)

属性说明类型默认值
TQueryFnData查询函数返回的结果数据,继承BaseRecordBaseRecordBaseRecord
TError自定义错误对象,继承HttpErrorHttpErrorHttpError
TSearchVariables搜索参数的值类型{}
TDataselect函数返回的结果数据,继承BaseRecord;未指定时默认使用TQueryFnDataBaseRecordTQueryFnData

返回值

属性说明类型
searchFormPropsAnt Design<Form>的属性FormProps<TSearchVariables>
tablePropsAnt Design<Table>的属性TableProps<TData>
tableQueryreact-queryuseQuery结果QueryObserverResult<{ data: TData[]; total: number; }, TError>
totalPage总页数(分页禁用时为undefinednumber \| undefined
currentPage当前页码状态(分页禁用时为undefinednumber \| undefined
setCurrentPage修改当前页的函数(分页禁用时为undefinedReact.Dispatch<React.SetStateAction<number>> \| undefined
pageSize当前每页条数(分页禁用时为undefinednumber \| undefined
setPageSize修改每页条数的函数(分页禁用时为undefinedReact.Dispatch<React.SetStateAction<number>> \| undefined
sorters当前排序状态CrudSorting
setSorters接受新排序状态的函数(sorters: CrudSorting) => void
filters当前筛选状态CrudFilters
setFilters接受新筛选状态的函数-(filters: CrudFilters, behavior?: "merge" \| "replace" = "merge") => void-(setter: (previousFilters: CrudFilters) => CrudFilters) => void
overtime超时加载属性{ elapsedTime?: number }

旧版 API 中的sorter/setSorter已废弃,请使用sorters/setSorters

完整示例

官方提供可直接运行的沙箱示例:

  • table-antd-use-table:本文所述基础用法(分页、排序、筛选)的完整项目,包含App.tsx、数据提供者配置与页面实现,可对照本文逐步阅读。
  • 端到端测试位于 cypress/e2e/table-antd-use-table,覆盖了列表加载、排序、筛选等交互断言,可作为验收标准参考。

总结

useTable是连接 Refine 数据层与 Ant Design 表格组件的桥梁:core 层负责分页、排序、筛选的状态管理与数据请求(基于useList),antd 层负责将其翻译成<Table>可消费的 props,并额外提供searchFormProps搜索表单能力。理解syncWithLocationinitial/permanentmodedefaultBehavior这几组核心概念,就能在不同业务场景(服务端/客户端分页与筛选、可分享的表格视图、实时刷新的看板)中游刃有余。官方文档 index.md、antd 实现 useTable.ts 与 core 实现 index.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),仅供参考

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

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

立即咨询