ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

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

2026/9/13 20:30:37 拓冰建站 浏览量
Refine v5 使用指南:useTable Hook 与 Ant Design 表格的分页、排序、筛选实战 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 DesignTable为主线结合refinedev/antd与refinedev/core的实际源码讲解useTable的完整用法、全部可配置属性、返回值语义以及常见业务场景关系数据展示、客户端筛选、客户端排序的解决方案。读完本文你将能够独立搭建一个支持服务端分页、排序、筛选与可分享 URL 状态的专业后台表格页面。认识 useTable基于 useList 的 Ant Design 表格适配层useTable是refinedev/antd包提供的 Hook通过它你可以在不手写任何数据请求逻辑的情况下获得与 Ant DesignTable组件兼容的全部属性。排序、筛选、分页等表格核心特性全部内置无需额外配置。从源码结构看useTable是分层架构中的 UI 适配层数据获取与状态管理核心在refinedev/core的useTable中实现底层通过useList发起数据请求refinedev/antd的useTable将 core 层的返回值映射为 Ant DesignTable所需的tableProps并额外提供searchFormProps搜索表单属性与onSearch搜索回调。这意味着core 层useTable的所有特性分页、排序、筛选、实时更新、超时检测等在 Ant Design 版本中全部可用同时你还能直接使用 Ant DesignTable的全部既有功能。在 packages/antd/src/hooks/table/useTable/useTable.ts 中可以看到antd 版本的useTable首先调用useTableCore然后在其返回值之上进行二次封装——例如用 Ant Design 的Form.useForm维护搜索表单状态、用Grid.useBreakpoint()根据响应式断点决定分页条的位置与形态。这正是“UI 适配层”一词的准确含义。基础用法在最基础的场景中useTable会直接返回接口endpoint返回的数据并默认从当前路由 URL 推断resourceimport { useTable } from refinedev/antd; import { Table } from antd; const { tableProps } useTableIPost(); Table {...tableProps} rowKeyid Table.Column dataIndexid titleID / Table.Column dataIndextitle titleTitle / /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 } useTableIPost(); Table {...tableProps} rowKeyid pagination{{ ...tableProps.pagination, position: [bottomCenter], size: small, }} {/* ... */} /Table;实现提示默认情况下分页在服务端完成每次翻页都会用新的currentPage与pageSize重新请求接口若要在客户端完成分页将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 dataIndextitle titleTitle 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 dataIndexstatus titleStatus filterDropdown{(props) ( FilterDropdown {...props} Radio.Group Radio valuepublishedPublished/Radio Radio valuedraftDraft/Radio Radio valuerejectedRejected/Radio /Radio.Group /FilterDropdown )} /在onChange中Ant Design 的列级筛选对象Recordstring, FilterValue | null通过mapAntdFilterToCrudFilter映射为 Refine 的CrudFilter[]进而触发setFilters。开启syncWithLocation后筛选状态同样与 URL 双向同步。初始排序与初始筛选Initial Filter and Sorter使用sorters.initial或filters.initial设置初始状态时务必同时为Table.Column添加getDefaultSortOrder或defaultFilteredValue否则 Hook 的内部状态可能与表格展示不同步const { tableProps, sorters, filters } useTable({ sorters: { initial: [ { field: title, order: asc, }, ], }, filters: { initial: [ { field: status, operator: eq, value: published, }, ], }, }); // --- Table.Column dataIndextitle titleTitle defaultSortOrder{getDefaultSortOrder(title, sorters)} / Table.Column dataIndexstatus titleStatus render{(value) TagField value{value} /} defaultFilteredValue{getDefaultFilter(status, filters)} filterDropdown{(props) ( FilterDropdown {...props} Radio.Group Radio valuepublishedPublished/Radio Radio valuedraftDraft/Radio Radio valuerejectedRejected/Radio /Radio.Group /FilterDropdown )} /查找某个字段的筛选值getDefaultFilterRefine 提供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是可选的传入后可以精确匹配指定运算符的筛选条件。自定义搜索表单SearchuseTable提供onSearch与searchFormProps两个属性来构建自定义筛选表单onSearch表单提交时被调用接收表单值并返回CrudFilters | PromiseCrudFilters调用后会把当前页重置为第 1 页searchFormProps需要传给 Ant DesignForm组件的属性。const { searchFormProps, tableProps } useTable({ onSearch: (values) { return [ { field: title, operator: contains, value: values.title, }, ]; }, }); List Form {...searchFormProps} layoutinline Form.Item nametitle Input placeholderSearch by title / /Form.Item SaveButton onClick{searchFormProps.form?.submit} / /Form Table {...tableProps} rowKeyid Table.Column titleTitle dataIndextitle / /Table /List;从 useTable.ts 的onFinish实现可以看到表单提交时onSearch返回的筛选条件会被setFilters接收若分页开启则同时setCurrentPage(1)。另外当开启syncWithLocation后Hook 会在 effect 中读取表单已注册字段把 URL 中对应的筛选值回填到表单见 useTable.ts实现“URL 即表单状态”的完整闭环。实时更新Realtime Updates该能力需要配置LiveProvider才能生效。当useTable挂载时它会用channel、resource等参数调用liveProvider的subscribe方法从而订阅实时更新。配合liveMode: auto收到相关事件后数据会自动刷新使用liveMode: manual则需手动处理。更多细节可参考liveProvider文档。在 antd 适配层中还有一个值得注意的细节tableProps.loading在liveMode auto时取isLoading否则取!isFetched见 useTable.ts这保证了实时模式下自动更新期间不会闪烁加载态。全部可配置属性Properties详解下表是useTable的完整配置项。所有属性都透传给 core 层useTableantd 层仅在此基础上增加onSearch见 useTable.ts 中的类型定义。resource默认从路由推断resource也可显式指定useTable({ resource: categories, });如果存在多个同名资源可以传identifier代替name作为主匹配键数据提供者的方法仍使用Refine/组件中定义的资源name。详见identifier说明。onSearchsearchFormProps.onFinish被调用时触发接收表单值返回CrudFilters | PromiseCrudFilters并将当前页重置为 1。适合做任意条件的自定义查询筛选示例见上文“自定义搜索表单”。dataProviderName存在多个dataProvider时用它指定当前资源使用哪一个useTable({ dataProviderName: second-data-provider, });pagination.currentPage设置初始页码默认1useTable({ pagination: { currentPage: 2, }, });pagination.pageSize设置初始每页条数默认10useTable({ pagination: { pageSize: 20, }, });pagination.mode取值off、server或client默认serveroff禁用分页拉取全部记录client客户端分页先拉取全部记录再在前端切分server服务端分页用currentPage与pageSize请求对应页数据。useTable({ pagination: { mode: client, }, });sorters.initial设置排序的初始值。initial不是持久的——用户一旦改变排序就会被清除。需要持久排序请用sorters.permanent。排序值类型参见CrudSorting接口useTable({ sorters: { initial: [ { field: name, order: asc, }, ], }, });sorters.permanent设置排序的持久值。permanent不可变——用户改变排序时不会被清除。需要临时排序请用sorters.initialuseTable({ sorters: { permanent: [ { field: name, order: asc, }, ], }, });在 core 层实现中permanent排序会被合并进最终传给useList的sorters通过unionSorters而initial只作为useState的初始值因此用户交互后initial会被覆盖见 packages/core/src/hooks/useTable/index.ts。sorters.mode取值off或server默认serveroff排序值不发送给服务端可在客户端用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.initialuseTable({ filters: { permanent: [ { field: name, operator: contains, value: Foo, }, ], }, });filters.defaultBehavior筛选行为取值merge默认或replacemerge新筛选与现有筛选合并——同一字段的新值替换旧值不同字段则追加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默认serveroff筛选值不发送给服务端可在客户端用filters自行筛选server服务端筛选用filters值请求数据。useTable({ filters: { mode: off, }, });syncWithLocation开启后useTable的状态排序、筛选、分页会自动编码进 URL 查询参数URL 变化时 Hook 状态也随之更新。这让表格状态可以跨路由/跨页面共享用户还能为特定表格视图加书签或分享链接。默认falseuseTable({ syncWithLocation: true, });注意syncWithLocation也可以在Refine/组件 上全局配置。antd 适配层会通过useSyncWithLocation()读取全局默认值并以 props 传入值为准见 useTable.ts。queryOptionsuseTable通过useList获取数据因此可以把queryOptions透传给它如retry、staleTime等useTable({ queryOptions: { retry: 3, }, });metameta用于向数据提供者方法传递附加信息常见用途针对特定用例定制数据提供者方法用纯 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会调用NotificationProvider的open方法弹出成功通知可用该属性自定义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表示已耗时毫秒请求完成后变为undefinedconst { 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 divthis takes a bit longer than expected/div; }返回值Return Values详解tableProps传给 Ant DesignTable组件的属性集合包含onChange用户对表格进行操作筛选、排序等时执行的回调。⚠️ 注意useTable正是通过该函数处理排序、筛选与分页的。如果你覆写onChange就必须手动处理这些操作。const { tableProps } useTable(); Table {...tableProps} onChange{tableProps.onChange} rowKeyid Table.Column titleTitle dataIndextitle / /Table;dataSource表格展示的数据即useList获取到的记录。loading数据是否正在获取。pagination分页配置值pageSize、currentPage、position等。scroll表格是否可滚动默认{ x: true }。searchFormProps返回 Ant DesignForm实例相关属性。当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 } useTableIPost, HttpError, ISearch({ onSearch: (values) { return [ { field: title, operator: contains, value: values.title, }, ]; }, }); return ( List Form {...searchFormProps} layoutinline Form.Item nametitle Input placeholderSearch by title / /Form.Item SaveButton onClick{searchFormProps.form?.submit} / /Form Table {...tableProps} rowKeyid Table.Column dataIndexid titleID / Table.Column titleTitle dataIndextitle / /Table /List ); };tableQuery即useList的返回值react-query的useQuery结果。sorters / setSorterssorters当前排序状态setSorters设置排序状态的函数类型为(sorters: CrudSorting) void。filters / setFiltersfilters当前筛选状态setFilters设置筛选状态的函数支持两种调用方式((filters: CrudFilters, behavior?: SetFilterBehavior) void) ((setter: (prevFilters: CrudFilters) CrudFilters) void);currentPage / setCurrentPage / pageSize / setPageSizecurrentPage当前页码状态分页禁用时为undefinedsetCurrentPageReact.DispatchReact.SetStateActionnumber | undefinedpageSize当前每页条数分页禁用时为undefinedsetPageSizeReact.DispatchReact.SetStateActionnumber | undefined。pageCount总页数状态分页禁用时为undefined。在 core 层通过Math.ceil(total / pageSize)计算见 index.ts。createLinkForSyncWithLocation生成syncWithLocation可用链接的函数接收SyncWithLocationParams并返回 URL 字符串(params: SyncWithLocationParams) string;overtime返回{ elapsedTime?: number }elapsedTime为已耗时毫秒请求完成后为undefinedconst { 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 DesignTable自带的列筛选功能完全兼容import { useTable } from refinedev/antd; import { Table } from antd; const ListPage () { const { tableProps } useTable({ filters: { mode: off, }, }); return ( Table {...tableProps} rowKeyid {/* ... */} Table.Column dataIndexstatus titleStatus filters{[ { text: Published, value: published, }, { text: Draft, value: draft, }, { text: Rejected, value: rejected, }, ]} onFilter{(value, record) record.status value} / /Table ); };如何做客户端排序将sorters.mode设为off以禁用服务端排序此时可完全使用 Ant DesignTable自带的列排序功能import { useTable } from refinedev/antd; import { Table } from antd; const ListPage () { const { tableProps } useTable({ sorters: { mode: off, }, }); return ( Table {...tableProps} rowKeyid Table.Column dataIndexid titleID sorter{(a, b) a.id - b.id} / {/* ... */} /Table ); };API 速查类型参数Type Parameters属性说明类型默认值TQueryFnData查询函数返回的结果数据继承BaseRecordBaseRecordBaseRecordTError自定义错误对象继承HttpErrorHttpErrorHttpErrorTSearchVariables搜索参数的值类型{}TDataselect函数返回的结果数据继承BaseRecord未指定时默认使用TQueryFnDataBaseRecordTQueryFnData返回值属性说明类型searchFormPropsAnt DesignForm的属性FormPropsTSearchVariablestablePropsAnt DesignTable的属性TablePropsTDatatableQueryreact-query的useQuery结果QueryObserverResult{ data: TData[]; total: number; }, TErrortotalPage总页数分页禁用时为undefinednumber \| undefinedcurrentPage当前页码状态分页禁用时为undefinednumber \| undefinedsetCurrentPage修改当前页的函数分页禁用时为undefinedReact.DispatchReact.SetStateActionnumber \| undefinedpageSize当前每页条数分页禁用时为undefinednumber \| undefinedsetPageSize修改每页条数的函数分页禁用时为undefinedReact.DispatchReact.SetStateActionnumber \| undefinedsorters当前排序状态CrudSortingsetSorters接受新排序状态的函数(sorters: CrudSorting) voidfilters当前筛选状态CrudFilterssetFilters接受新筛选状态的函数-(filters: CrudFilters, behavior?: merge \| replace merge) void-(setter: (previousFilters: CrudFilters) CrudFilters) voidovertime超时加载属性{ elapsedTime?: number }旧版 API 中的sorter/setSorter已废弃请使用sorters/setSorters。完整示例官方提供可直接运行的沙箱示例table-antd-use-table本文所述基础用法分页、排序、筛选的完整项目包含App.tsx、数据提供者配置与页面实现可对照本文逐步阅读。端到端测试位于 cypress/e2e/table-antd-use-table覆盖了列表加载、排序、筛选等交互断言可作为验收标准参考。总结useTable是连接 Refine 数据层与 Ant Design 表格组件的桥梁core 层负责分页、排序、筛选的状态管理与数据请求基于useListantd 层负责将其翻译成Table可消费的 props并额外提供searchFormProps搜索表单能力。理解syncWithLocation、initial/permanent、mode、defaultBehavior这几组核心概念就能在不同业务场景服务端/客户端分页与筛选、可分享的表格视图、实时刷新的看板中游刃有余。官方文档 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),仅供参考