Refine v5 useTable 实战指南:用 Ant Design Table 快速构建支持分页、排序与筛选的后台列表页
【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards & B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine
useTable是 Refine v5 中面向 Ant Design 的表格 Hook,它返回与 Ant Design<Table>示例为实战主线,结合 packages/antd/src/hooks/table/useTable/useTable.ts 源码与 完整 Hook 文档,讲清楚它的用法、配置项、返回值以及常见问题的解法,读完即可在真实 React 管理后台中落地一套完整的列表页。
为什么选择 useTable
在 Refine v5 中,useTable让你"不需要为排序、筛选和分页做任何额外操作",就能拿到与 Ant Design<Table>组件兼容的全部属性。它的核心定位可以从源码中的注释得到印证:
By using useTable, you are able to get properties that are compatible with Ant Design
<Table>component. All features such as sorting, filtering and pagination comes as out of box.
从 useTable.ts 的实现来看,它本身是@refinedev/core中useTable的扩展:内部先调用核心层的useTableCore完成数据请求与状态管理,再通过mapAntdSorterToCrudSorting、mapAntdFilterToCrudFilter把 Ant Design 的表格交互翻译成 Refine 的CrudSorting/CrudFilters。而数据获取这一层,底层走的是useListHook,也就是说表格数据本质上是一次getList数据提供者调用。
useTable完整可用性由单元测试覆盖,见 useTable.spec.tsx:测试用例覆盖了默认渲染、自定义分页初始值、自定义资源、syncWithLocation、setFilters手动设置筛选、defaultBehavior: "replace"、分页模式client/server/off、从 URL 参数回填搜索表单等场景,可作为理解其行为边界的权威参考。
快速上手:最小可用列表页
以示例项目 examples/table-antd-use-table 为例,其数据源为https://api.fake-rest.refine.dev,资源为posts(见 App.tsx)。
一个最小可用的useTable列表页大致如下:
import { List, useTable } from "@refinedev/antd"; import { Table } from "antd"; export const PostList = () => { const { tableProps } = useTable<IPost>(); return ( <List> <Table {...tableProps} rowKey="id"> <Table.Column dataIndex="id" title="ID" /> <Table.Column dataIndex="title" title="Title" /> </Table> </List> ); };这里有几个要点:
tableProps展开后自动携带dataSource、loading、onChange、pagination、scroll: { x: true }等<Table>需要的属性,无需手动接线;- 默认情况下,
useTable会从当前路由推断resource(示例中/posts路由对应posts资源),无需显式声明; rowKey="id"是 Ant Design<Table>渲染所必需的,通常由使用方提供。
分页:服务端分页与三种模式
分页能力由tableProps.pagination开箱即用地提供。从 useTable.ts 源码可以看到,antdPagination()会生成完整的TablePaginationConfig:包含current(当前页)、pageSize、total(来自data?.total)、以及响应式位置(小屏居底居中、大屏居右下)。更关键的是,它会通过createLinkForSyncWithLocation为页码生成真实链接而非纯状态切换,并覆盖<Table>默认的pagination.itemRender。
覆盖分页配置
如果你想调整<Table>的分页展示,正确的姿势是把tableProps.pagination展开后再覆盖:
const { tableProps } = useTable<IPost>(); <Table {...tableProps} rowKey="id" pagination={{ ...tableProps.pagination, position: ["bottomCenter"], size: "small", }} > {/* columns */} </Table>分页模式(pagination.mode)
pagination.mode可选"server"(默认)、"client"、"off":
- server:默认模式,服务端分页,请求携带
currentPage与pageSize; - client:客户端分页,一次性拉取全部记录后在浏览器端分页;
- off:禁用分页,拉取所有记录,此时
tableProps.pagination为false(源码中isPaginationEnabled为false时antdPagination()直接返回false)。
useTable({ pagination: { mode: "client", }, });对应测试可见 useTable.spec.tsx:client/server模式返回{ pageSize: 10, current: 1 },而off模式下tableProps.pagination为假值。
初始分页值
useTable({ pagination: { currentPage: 2, // 默认 1 pageSize: 20, // 默认 10 }, });排序:列级 sorter 与服务端排序
要给列开启排序,只需给对应的<Table.Column>加上sorter属性,useTable会自动把 Ant Design 的排序状态映射为CrudSorting并随请求发送给服务端:
<Table.Column dataIndex="id" title="ID" sorter />有几个值得注意的细节:
- 请求中使用的字段名优先取
<Column>的key,没有key时回退到dataIndex,因此当dataIndex与排序字段名不一致时可以用key指定; - 多列排序时,
sorter需要传入{ multiple: 1 }、{ multiple: 2 }等值来声明优先级。示例 list.tsx 中content列为multiple: 1、title列为multiple: 2; - 排序状态同样支持通过
syncWithLocation同步到 URL。
初始排序与永久排序
sorters.initial用于设置初始排序,它会被用户的后续操作清除;如果需要不可修改的固定排序,使用sorters.permanent:
useTable({ sorters: { initial: [{ field: "name", order: "asc" }], // permanent: [{ field: "name", order: "asc" }], }, });sorters.mode可设为"off"或"server"(默认)。设为"off"时排序值不发送给服务端,可与 Ant Design<Table>的客户端排序(如sorter={(a, b) => a.id - b.id})配合使用,适合数据量小的场景。
筛选:FilterDropdown 与列级筛选
列级筛选依赖<Table.Column>的filterDropdown属性,把筛选表单放入 Refine 提供的<FilterDropdown>组件中,并将函数入参透传给该组件:
<Table.Column dataIndex="status" title="Status" render={(value: string) => <TagField value={value} />} filterDropdown={(props) => ( <FilterDropdown {...props}> <Radio.Group> <Radio value="published">Published</Radio> <Radio value="draft">Draft</Radio> <Radio value="rejected">Rejected</Radio> </Radio.Group> </FilterDropdown> )} />在 list.tsx 中可以同时看到字符串筛选(Radio.Group)、文本包含筛选(Input)和关联数据多选筛选(Select mode="multiple")三种形态;其中关联筛选通过mapValue把选项值转换为数字后再交给FilterDropdown。
初始筛选、默认值与 helper 函数
设置了filters.initial时,必须配合getDefaultSortOrder/defaultFilteredValue让表格列状态与 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" defaultFilteredValue={getDefaultFilter("status", filters)} filterDropdown={(props) => ( <FilterDropdown {...props}>{/* ... */}</FilterDropdown> )} />;getDefaultSortOrder(field, sorters)与getDefaultFilter(field, filters, operator?)是@refinedev/antd导出的工具函数,用于从当前sorters/filters状态中取出指定字段的默认值;- 示例 list.tsx 一次性配置了三个初始筛选(
title包含空串、status等于draft、category.id属于[1, 2])和一个初始排序(title升序),并全部回填到列上,是完整的参考实现。
筛选行为与永久筛选
filters.initial会被后续操作清除,filters.permanent则恒定生效。filters.defaultBehavior控制setFilters时的合并策略,默认"merge"(相同字段替换、不同字段追加),可改为"replace"(整体替换):
useTable({ filters: { defaultBehavior: "replace", }, });对应测试 useTable.spec.tsx 验证了"replace"模式下新筛选会完全覆盖旧筛选。filters.mode与sorters.mode类似,可设为"off"关闭服务端筛选,配合<Table.Column>的filters+onFilter做纯客户端筛选。
搜索表单:onSearch 与 searchFormProps
useTable还额外提供了onSearch配置与searchFormProps返回值,用来构造独立的搜索表单。onSearch接收表单提交值并返回CrudFilters或Promise<CrudFilters>,提交后自动把页码重置为 1(见 useTable.ts 中onFinish的实现):
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 = () => { const { searchFormProps, tableProps } = useTable<IPost, HttpError, ISearch>({ onSearch: (values) => [ { 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> ); };searchFormProps展开后即 Ant Design<Form>的 props,其onFinish已被useTable接管;配合syncWithLocation时,URL 参数中的筛选值会自动回填到表单字段(useTable.ts 中通过注册表单字段与filters的匹配完成回填,测试见 useTable.spec.tsx)。
URL 状态同步:syncWithLocation
syncWithLocation是后台管理场景的高频需求:开启后,分页、排序、筛选状态会编码进 URL 查询参数,URL 变化时表格状态自动跟随,从而支持分享、收藏与刷新后状态保持。可以在 Hook 级开启:
useTable({ syncWithLocation: true, });也可以在<Refine>组件上全局开启(示例 App.tsx 即使用全局配置options.syncWithLocation: true)。此时antdPagination()生成的页码链接会携带完整的pagination、sorters、filters状态,实现真正的"可分享的表格视图"。
数据请求与运行时配置
由于底层使用useList(即 React Query 的useQuery),useTable透传了一系列数据请求与实时能力:
useTable({ resource: "categories", // 显式指定资源,默认从路由推断 dataProviderName: "second-data-provider", // 多数据提供者时选择目标 queryOptions: { retry: 3 }, // 透传给 useQuery meta: { headers: { "x-meta-data": "true" } }, // 传给数据提供者方法的附加信息 successNotification: (data, values, resource) => ({ message: `${data.title} Successfully fetched.`, description: "Success with no errors", type: "success", }), errorNotification: (data, values, resource) => ({ message: `Something went wrong when getting ${data.id}`, description: "Error", type: "error", }), });resource:默认从路由推断;多资源同名时可用identifier指定主匹配键;meta:可向getList等数据提供者方法传递额外信息(如自定义请求头、GraphQL 查询字段),数据提供者从方法参数中读取meta;successNotification/errorNotification:依赖NotificationProvider,自定义请求成功 / 失败的通知内容;liveMode、onLiveEvent、liveParams:依赖LiveProvider,用于实时订阅数据更新(liveMode: "auto"自动刷新、"manual"手动处理);overtimeOptions:设置请求超时提示,interval为轮询间隔(毫秒),onInterval为回调,返回的overtime.elapsedTime在请求完成后变为undefined,可用于"请求超过 4 秒显示提示"之类的交互。
返回值速查
useTable的返回值中,除tableProps与searchFormProps外,还暴露了可编程控制的状态:
| 返回值 | 说明 |
|---|---|
tableProps | 直接传给<Table>的 props(含dataSource、loading、onChange、pagination、scroll)。注意onChange内部负责排序 / 筛选 / 分页处理,覆盖后需自行实现这些逻辑 |
searchFormProps | Ant Design<Form>props,onFinish触发onSearch |
tableQuery | 底层useList(React QueryuseQuery)的返回值,如isSuccess、isFetching |
sorters/setSorters | 当前排序状态与更新函数 |
filters/setFilters | 当前筛选状态与更新函数(支持merge/replace行为或函数式更新) |
currentPage/setCurrentPage | 当前页码及更新函数(分页关闭时为undefined) |
pageSize/setPageSize | 每页条数及更新函数(分页关闭时为undefined) |
pageCount | 总页数(分页关闭时为undefined) |
createLinkForSyncWithLocation | 根据pagination/sorters/filters生成可分享链接的函数 |
overtime | { elapsedTime?: number },超时加载状态 |
这些返回值在 useTable.ts 中有完整定义,类型签名(如setFilters支持behavior?: "merge" | "replace")可参考 Hook 文档 的 API 一节。
常见问题
如何处理关联数据?当列表列需要展示关联资源名称(如category.id→ 分类标题)时,用useMany批量拉取关联数据,并用useSelect配合getDefaultFilter构建分类多选筛选。示例 list.tsx 展示了完整做法:从tableProps.dataSource收集category.id列表 →useMany取分类 → 渲染时查表映射标题。
如何做纯客户端筛选 / 排序?分别设置filters.mode: "off"与sorters.mode: "off",即可关闭服务端筛选 / 排序,完全复用 Ant Design<Table>自带的filters+onFilter、sorter客户端能力,适合数据量不大、不希望每次交互都发请求的场景。
小结
useTable把"表格数据获取 + 分页 + 排序 + 筛选 + 搜索 + URL 状态同步"完整收敛到一个 Hook 中:外层是 Ant Design<Table>的零成本接入,内层是 Refine 核心的useList数据请求与CrudSorting/CrudFilters统一模型。无论是用 table-antd-use-table 示例 快速起步,还是深入 useTable 源码 理解其映射逻辑,都能在真实后台项目中显著减少列表页的样板代码。
【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards & B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考