news 2026/9/12 16:00:03

Refine 与 TanStack Table 集成实战:用 useTable 实现列级筛选(Column Filtering)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Refine 与 TanStack Table 集成实战:用 useTable 实现列级筛选(Column Filtering)

Refine 与 TanStack Table 集成实战:用 useTable 实现列级筛选(Column Filtering)

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

本文围绕 Refine 仓库中 @refinedev/react-table 的列级筛选 Live Preview 示例(documentation/docs/packages/tanstack-table/examples/_partial-filtering-live-preview.md)展开,讲解如何用useTable把 TanStack Table 的列筛选状态与 Refine 的服务端过滤(CrudFilters)打通,并通过meta.filterOperator精确控制每个字段的过滤算子。读完本文你将掌握:如何在 headless 表格中渲染可筛选表头、如何将列筛选翻译成 Refine 数据提供者能识别的过滤条件,以及条件过滤(and/or)和filterKey的使用方式。

一、背景:无头表格适配器如何工作

Refine 通过 TanStack Table 集成介绍 提供了@refinedev/react-table这个适配器包,它让你在使用 TanStack Table 的同时,自动继承 RefineuseTable的分页、排序、过滤等全部能力。底层数据获取走的是useList(即 data provider 的getList),同时因为它被设计为headless,UI 渲染完全由你控制。

安装方式:

npm install @refinedev/react-table

从源码看,这个适配器的入口定义在 packages/react-table/src/useTable/index.ts:

  • 内部先调用核心包的useTableuseTableCore),拿到filterssetFilterssorterscurrentPage等 Refine 状态;
  • 再调用useReactTable创建 TanStack Table 实例;
  • 通过一组工具函数在「TanStack 列筛选状态(ColumnFiltersState)」与「Refine 过滤状态(CrudFilters)」之间做双向翻译。

其中的关键开关:

  • manualPagination: true:分页始终由服务端(Refine 数据提供者)控制;
  • manualSorting: isServerSideFilteringEnabled:只有当sorters.mode不是"off"时才开启服务端排序;
  • manualFiltering: isServerSideFilteringEnabled:只有当filters.mode不是"off"时才开启服务端过滤(此时getFilteredRowModel()不会被挂载)。

也就是说,默认情况下每次筛选变化都会触发一次新的数据请求。

二、核心示例:可筛选的 PostList 表格

下面是被嵌入useTable文档 Filtering 小节的可运行示例,去掉 Live Preview 包装后的核心代码(完整源码见 examples/_partial-filtering-live-preview.md):

import React from "react"; import { useTable } from "@refinedev/react-table"; import { ColumnDef, flexRender } from "@tanstack/react-table"; interface IPost { id: number; title: string; content: string; status: "published" | "draft" | "rejected"; } const PostList: React.FC = () => { const columns = React.useMemo<ColumnDef<IPost>[]>( () => [ { id: "id", header: "ID", accessorKey: "id", enableColumnFilter: false, // 该列禁用列筛选 }, { id: "title", header: "Title", accessorKey: "title", meta: { filterOperator: "contains", // 使用模糊包含匹配 }, }, { id: "status", header: "Status", accessorKey: "status", meta: { filterOperator: "contains", }, }, { id: "createdAt", header: "CreatedAt", accessorKey: "createdAt", meta: { filterOperator: "gte", // 使用大于等于 }, }, ], [], ); const { reactTable: { getHeaderGroups, getRowModel }, } = useTable({ columns, }); return ( <table> <thead> {getHeaderGroups().map((headerGroup) => ( <tr key={headerGroup.id}> {headerGroup.headers.map((header) => { return ( <th key={header.id}> {header.isPlaceholder ? null : ( <> {flexRender( header.column.columnDef.header, header.getContext(), )} {header.column.getCanFilter() ? ( <div> <input value={ (header.column.getFilterValue() as string) ?? "" } onChange={(e) => header.column.setFilterValue(e.target.value) } /> </div> ) : null} </> )} </th> ); })} </tr> ))} </thead> <tbody> {getRowModel().rows.map((row) => { return ( <tr key={row.id}> {row.getVisibleCells().map((cell) => { return ( <td key={cell.id}> {flexRender(cell.column.columnDef.cell, cell.getContext())} </td> ); })} </tr> ); })} </tbody> </table> ); };

这个示例演示了三个关键知识点:

  1. 筛选 UI 由你手写header.column.getCanFilter()判断该列是否允许筛选(对应enableColumnFilter: falseid列就不会渲染输入框);
  2. 受控输入绑定:输入框的value来自header.column.getFilterValue()onChange调用header.column.setFilterValue()
  3. meta.filterOperator决定服务端算子title/status"contains"createdAt"gte"id列明确关闭筛选。

三、从列筛选到 CrudFilters:底层翻译逻辑

TanStack Table 的筛选状态只是一个{ id, value }列表,但 Refine 的数据提供者需要的是带operatorCrudFiltersLogicalFilterConditionalFilter)。二者之间的翻译由适配器自动完成。

3.1 默认算子

在 packages/react-table/src/utils/column-filters-to-crud-filters/index.ts 中,翻译规则为:

  • 优先读取列定义meta.filterOperator
  • 若未声明,则根据值的类型取默认:值为数组时用"in",否则用"eq"

测试用例 column-filters-to-crud-filters/index.spec.ts 直接验证了这一行为:{ id: "name", value: "John" }会翻译成{ field: "name", operator: "eq", value: "John" },而数组值["John", "Doe"]会被翻译成operator: "in"

3.2 组合过滤(and / or)

meta.filterOperator"and""or",且筛选值是一个数组时,会产生ConditionalFilter结构:

{ key: filterKey, // 默认取列 id operator: "or", // 或 "and" value: [ ...子过滤条件... ], }

crudFiltersToColumnFilters反向翻译时,会通过meta.filterKey找到对应的列 id(见 crud-filters-to-column-filters/index.ts)。适配器测试对「组合过滤」和「自定义filterKey」都有覆盖(index.spec.ts)。

3.3 已移除筛选的同步

当某列筛选被清空时,适配器通过getRemovedFilters把对应条件以value: undefined的形式补回,确保 Refine 侧的filters状态被完整同步(见 get-removed-filters/index.ts)。

3.4 筛选变化时的行为

在 useTable/index.ts 中,useEffect监听columnFilters,将其翻译后通过setFilters写入 Refine;若当前存在筛选且分页开启,还会自动把页码重置回第 1 页(setCurrentPage(1)),避免停留在筛选结果之外的分页上。

四、配套属性:如何控制过滤行为

以下属性都在useTable({ refineCoreProps: {...} })中配置,详见 useTable Hook 文档:

属性取值默认值说明
filters.mode"server"/"off""server""off"时不把筛选发送给服务端,配合 TanStack Table 客户端筛选(getFilteredRowModel会被启用)
filters.initialCrudFilter[]初始筛选值,用户修改后即被清除
filters.permanentCrudFilter[]永久筛选值,不可被用户操作清除
filters.defaultBehavior"merge"/"replace""replace"新筛选如何与现有筛选合并:"merge"按列合并,"replace"整体替换
syncWithLocationboolean来自<Refine>开启后筛选、排序、分页状态会编码进 URL query,支持分享/书签特定表格视图

服务端过滤开关manualFiltering正是由filters.mode推导而来(useTable/index.ts),这解释了为什么filters.mode: "off"可以无缝切换到 TanStack 自带的客户端过滤——此时适配器不再把getFilteredRowModel禁用。

读取当前筛选值

Refine 提供了getDefaultFilter工具,从refineCore.filters中取出指定字段的筛选值:

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

getDefaultFilter的实现位于核心包的src/definitions/table/index.ts(对应 packages/core 目录),适合在需要把筛选值回填到表单、下拉框或自定义筛选面板时使用。

五、实践要点与注意事项

5.1 完整示例运行环境

该 Live Preview 示例的宿主代码会在启动时执行setInitialRoutes(["/posts"]),并将PostList挂载到<RefineHeadlessDemo>下的/posts路由。你需要一个带posts资源的 Refine 应用,例如参考仓库中的 table-react-table-basic 示例(useTable文档底部即引用此示例)。resource默认从当前路由读取,也可以通过refineCoreProps.resource显式指定。

5.2 常见问题

  • 为什么id列不显示筛选框?因为设置了enableColumnFilter: falsegetCanFilter()返回false
  • 为什么每次输入都触发请求?默认filters.mode: "server",列筛选状态一旦变化就会通过setFilters触发getList重新请求;如果希望纯客户端过滤,设置filters.mode: "off"即可(FAQ 章节 也推荐了这一做法);
  • filterOperator必须是CrudOperators类型:包括eqneltltegtgtecontainsncontainsinninbetweenandor等,具体以核心包的CrudOperators定义为准;
  • 组合过滤的key:默认取列 id;多个相同算子并存时,用meta.filterKey区分不同条件。

六、小结

列级筛选是 TanStack Table 与 Refine 数据层整合最典型的场景:UI 完全 headless(一个<input>即可),状态同步则由@refinedev/react-table的工具函数自动完成。掌握了meta.filterOperatorfilters.modesyncWithLocation这几个核心配置,你就可以在任何 UI 库(Ant Design、MUI、Mantine、Chakra UI 或原生 HTML)之上快速构建出支持服务端过滤、可分享 URL 状态的管理后台表格。

延伸阅读:本示例是 useTable Hook 文档 中 Filtering 小节的组成部分,同系列还有分页(Pagination)、排序(Sorting)、关联数据(Relational)等 Live Preview 示例,均位于 documentation/docs/packages/tanstack-table/examples 目录下;相关可运行工程可参考 examples/table-react-table-basic。

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

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/12 16:00:01

NLP技术演进:从基础概念到Transformer实战应用

1. NLP概述&#xff1a;从基础概念到技术演进自然语言处理&#xff08;Natural Language Processing, NLP&#xff09;作为人工智能领域最具挑战性的分支之一&#xff0c;其核心目标是让计算机能够理解、解释和生成人类语言。我第一次接触NLP是在2016年处理客服工单分类项目时&…

作者头像 李华
网站建设 2026/9/12 15:57:03

ANT9921 H类30W单声道功放芯片深度解析

1. 为什么一块30W单声道功放芯片&#xff0c;值得花一整篇讲清楚&#xff1f; ANT9921这个名字&#xff0c;在音频硬件圈子里不算响亮——它没有TPA3116那种铺天盖地的淘宝爆款标签&#xff0c;也不像MAX98357A那样被树莓派玩家当“默认配置”来用。但如果你真在做一款便携式蓝…

作者头像 李华
网站建设 2026/9/12 15:55:13

微信机器人接口框架/开源

在微信深度渗透私域流量与社群运营的背景下&#xff0c;WTAPI社群机器人API凭借其“高稳定、易开发、强扩展”的技术特性&#xff0c;为开发者提供了覆盖营销系统、智能客服、自定义机器人等核心场景的微信二次开发解决方案。以下结合用户核心需求与WTAPI技术优势&#xff0c;系…

作者头像 李华
网站建设 2026/9/12 15:55:02

车辆行驶过程中如何获得准确位置信息?——GNSS PVT POS 算法(3)

四. 历元间差分算法 在之前的文章中有提到双核或者双任务场景下&#xff0c;针对RTK耗时较长、内存空间存储有限等问题&#xff0c;会在PVT任务中基于RTK上报的高精度定位点信息差分出实时高频的定位结果信息&#xff0c;在此过程中使用到的算法就是历元间差分算法。4.1 载波历…

作者头像 李华