TanStack Table React 模糊过滤(Fuzzy Filtering)完整实战指南
【免费下载链接】table🤖 Headless UI for building powerful tables & datagrids for TS/JS - React-Table, Vue-Table, Solid-Table, Svelte-Table项目地址: https://gitcode.com/gh_mirrors/ta/table
模糊过滤(Fuzzy Filtering)是一种基于近似匹配的过滤技术,允许用户在搜索数据时不必输入精确值,即可命中相似结果,是现代数据表格中搜索体验的关键一环。本指南基于 TanStack Table React 官方文档,深入讲解如何利用@tanstack/match-sorter-utils库为表格定义自定义模糊过滤函数、将其接入全局过滤与列过滤,并结合匹配排名信息实现"按相关度排序"的完整链路。读完本文,你将掌握从功能装配(tableFeatures)、类型安全(filterMeta插槽)到 UI 接线与源码原理的整套实战方案,可直接复刻到自己的数据表格项目中。
前置准备与示例工程
在动手实现之前,建议先查看仓库中已完成的官方示例,直观理解模糊过滤在真实表格中的表现:
- Fuzzy Search React 示例:完整的可运行工程,包含 5000 行数据的表格、全局搜索框、每列过滤输入框、分页与排名排序效果。
- Global Filtering(React)指南:模糊过滤最常见的落地场景——全局过滤,两个文档配合阅读效果更佳。
示例工程的入口源码位于 examples/react/filters-fuzzy/src/main.tsx,其中通过columnHelper定义了四列:id(精确匹配equalsString)、firstName(大小写敏感包含includesStringSensitive)、lastName(大小写不敏感包含includesString),以及核心的fullName(模糊过滤 + 模糊排序)。同时它注册了rowPaginationFeature与分页行模型,演示模糊过滤与分页共存。
安装依赖
使用模糊过滤前,需要安装两个包:
npm install @tanstack/react-table @tanstack/match-sorter-utils[!NOTE]
@tanstack/match-sorter-utils是 Kent C. Dodds 的 match-sorter 库的 TanStack 分支,专为适配 TanStack Table 逐行过滤(row by row filtering)的工作方式而 fork。它提供rankItem与compareItems两个底层工具:先用rankItem给单个条目打分,再用返回的RankingInfo中的passed字段决定是否过滤、用rank字段决定排序。这正是它区别于"一次完成过滤+排序"的传统 match-sorter API 的关键设计——在源码头部注释中写明了这一增量式(incrementally applied)使用方式(见 packages/match-sorter-utils/src/index.ts)。
功能装配:在 tableFeatures 中启用模糊过滤
TanStack Table v9 采用功能组合(feature composition)的架构。添加模糊过滤相关功能后,对应的 API 才会启用。若使用客户端模糊过滤与排序,必须在对应功能之后挂载filteredRowModel和sortedRowModel,因为行模型插槽是经过类型检查(type-checked)的。
import { useTable, tableFeatures, columnFilteringFeature, globalFilteringFeature, rowSortingFeature, createFilteredRowModel, createSortedRowModel, metaHelper, } from '@tanstack/react-table' const features = tableFeatures({ columnFilteringFeature, globalFilteringFeature, rowSortingFeature, filteredRowModel: createFilteredRowModel(), // if using client-side filtering // manualFiltering: true, // if using manual server-side filtering sortedRowModel: createSortedRowModel(), // if using client-side sorting // manualSorting: true, // if using manual server-side sorting filterFns: { fuzzy: fuzzyFilter }, sortFns: { fuzzy: fuzzySort }, filterMeta: metaHelper<FuzzyFilterMeta>(), }) const table = useTable({ features, columns, data, })各配置项含义如下:
| 配置项 | 作用 | 备注 |
|---|---|---|
columnFilteringFeature | 启用列过滤 | 全局过滤依赖列过滤,必须先注册(参考 global-filtering.md) |
globalFilteringFeature | 启用全局过滤 | 依赖columnFilteringFeature |
rowSortingFeature | 启用排序 | 为按模糊排名排序提供能力 |
filteredRowModel | 客户端过滤行模型 | 使用客户端过滤时必须提供,否则行模型插槽类型检查不通过 |
manualFiltering | 服务端手动过滤开关 | 数据已由服务端过滤时置为true,跳过内置过滤逻辑 |
sortedRowModel | 客户端排序行模型 | 使用客户端排序时必须提供 |
manualSorting | 服务端手动排序开关 | 数据已由服务端排序时置为true |
filterFns | 过滤函数注册表 | 按字符串名引用过滤函数 |
sortFns | 排序函数注册表 | 按字符串名引用排序函数 |
filterMeta | 过滤元数据类型插槽 | 用metaHelper<FuzzyFilterMeta>()声明类型,见下文 |
[!NOTE] 上面的
filterFns与sortFns注册表只列出了本指南用到的自定义fuzzy函数。虽然展开全部内置注册表(filterFns: { ...filterFns, fuzzy: fuzzyFilter })仍然可用,但会把每个内置函数都打进你的 bundle。最佳实践是只注册实际用到的函数,或者完全不注册、直接把函数传给列的filterFn与sortFn选项。
定义自定义模糊过滤函数
模糊过滤的核心是一个自定义过滤函数:它接收行(row)、列 ID(columnId)与过滤值(value),返回布尔值决定该行是否保留。同时它通过addMeta回调把排名信息附加到行上,供后续排序使用。
定义过滤元数据类型
首先定义过滤元数据的形状,以及携带它的 features 类型:
import { rankItem } from '@tanstack/match-sorter-utils' import type { RankingInfo } from '@tanstack/match-sorter-utils' import type { FilterFn, RowData, TableFeatures } from '@tanstack/react-table' interface FuzzyFilterMeta { itemRank?: RankingInfo } // A features type that carries the filterMeta shape type FuzzyFeatures = TableFeatures & { filterMeta: FuzzyFilterMeta }实现模糊过滤函数
const fuzzyFilter: FilterFn<FuzzyFeatures, RowData> = ( row, columnId, value, addMeta, ) => { // Rank the item const itemRank = rankItem(row.getValue(columnId), value) // Store the itemRank info addMeta?.({ itemRank, }) // Return if the item should be filtered in/out return itemRank.passed }函数逻辑分三步:
- 打分:调用
rankItem(row.getValue(columnId), value),将当前单元格的值与搜索词比较,得到RankingInfo。从@tanstack/match-sorter-utils的源码看,rankItem内部会依次尝试大小写敏感相等、相等、开头匹配、单词开头匹配、包含、首字母缩略词等策略,最终给出 0(NO_MATCH)到 7(CASE_SENSITIVE_EQUAL)之间的排名分数(见 packages/match-sorter-utils/src/index.ts)。 - 存元数据:
addMeta?.(...)是可选回调,因此用可选链调用;调用后排名信息会写入该行对应列的columnFiltersMeta[columnId]中。 - 决定去留:返回
itemRank.passed。passed是rank >= threshold的结果,默认阈值是rankings.MATCHES(值为 1),即只要存在松散匹配就算通过(见 packages/match-sorter-utils/src/index.ts)。
在 tableFeatures 中注册
要以字符串名'fuzzy'引用该过滤函数,并让存储的过滤元数据获得正确类型,需要在tableFeatures调用中通过filterFns与filterMeta插槽同时注册:
import { tableFeatures, metaHelper } from '@tanstack/react-table' const features = tableFeatures({ columnFilteringFeature, globalFilteringFeature, rowSortingFeature, filteredRowModel: createFilteredRowModel(), sortedRowModel: createSortedRowModel(), filterFns: { fuzzy: fuzzyFilter }, sortFns: { fuzzy: fuzzySort }, filterMeta: metaHelper<FuzzyFilterMeta>(), })这里不需要任何declare module全局模块增强。从源码实现看,filterMeta插槽是一个类型优先(type-only)的槽位:当 features 对象通过filterMeta声明了元数据类型时,ExtractFilterMeta类型工具会让该类型生效;否则回退到全局声明合并的FilterMeta接口(见 packages/table-core/src/features/column-filtering/columnFilteringFeature.types.ts)。filterFns与filterMeta插槽的作用域都限定在该 features 对象内,只会影响用该 features 创建的表格,不会污染全局类型环境。
模糊过滤 + 全局过滤
模糊过滤最典型的应用场景是全局过滤(Global Filtering)——一个搜索词同时匹配所有参与全局过滤的列。做法是:在tableFeatures的filterFns插槽注册模糊过滤函数,再在表格的globalFilterFn选项中按名称引用它:
import { useTable, tableFeatures, columnFilteringFeature, globalFilteringFeature, rowSortingFeature, createFilteredRowModel, createSortedRowModel, metaHelper, } from '@tanstack/react-table' const features = tableFeatures({ columnFilteringFeature, globalFilteringFeature, rowSortingFeature, filteredRowModel: createFilteredRowModel(), sortedRowModel: createSortedRowModel(), // needed if you want sorting with fuzzy rank filterFns: { fuzzy: fuzzyFilter }, sortFns: { fuzzy: fuzzySort }, filterMeta: metaHelper<FuzzyFilterMeta>(), }) const table = useTable({ features, columns, data, globalFilterFn: 'fuzzy', })注意globalFilteringFeature依赖columnFilteringFeature,两者的注册顺序不能颠倒。TanStack Table 不会自动渲染全局过滤输入框,需要自己添加 UI:通过table.state.globalFilter响应式读取当前值,用table.setGlobalFilter更新。官方示例使用了一个基于useDebouncedCallback(来自@tanstack/react-pacer)的DebouncedInput组件,默认 500ms 防抖,避免每次按键都触发对 5000 行数据的过滤计算(见 examples/react/filters-fuzzy/src/main.tsx)。
关于全局过滤的更多细节(globalFilter状态管理、外部 atom 接管、enableGlobalFilter禁用开关等),请参见 全局过滤指南。
模糊过滤 + 列过滤
模糊过滤同样可以作用于单列。将模糊过滤函数注册到tableFeatures的filterFns插槽(见上文装配一节)后,在列定义中通过filterFn选项按名称指定即可:
const column = [ { accessorFn: (row) => `${row.firstName} ${row.lastName}`, id: 'fullName', header: 'Full Name', cell: (info) => info.getValue(), filterFn: 'fuzzy', // using our custom fuzzy filter function }, // other columns... ]这个例子把模糊过滤应用在拼接firstName与lastName生成的fullName列上——用户只需输入名或姓的一部分,即可模糊命中。在官方示例中,这一列同时搭配了sortFn: 'fuzzy',形成"过滤 + 按相关度排序"的完整体验(见 examples/react/filters-fuzzy/src/main.tsx)。
结合模糊排名排序
使用列过滤的模糊过滤时,你可能还希望基于排名信息对结果排序——让最接近搜索词的行排在最前面。定义一个自定义排序函数即可:
import { compareItems } from '@tanstack/match-sorter-utils' import { sortFn_alphanumeric } from '@tanstack/react-table' import type { SortFn } from '@tanstack/react-table' const fuzzySort: SortFn<FuzzyFeatures, Person> = (rowA, rowB, columnId) => { let dir = 0 // Only sort by rank if the column has ranking information if (rowA.columnFiltersMeta[columnId]) { dir = compareItems( rowA.columnFiltersMeta[columnId].itemRank!, rowB.columnFiltersMeta[columnId].itemRank!, ) } // Provide an alphanumeric fallback for when the item ranks are equal return dir === 0 ? sortFn_alphanumeric(rowA, rowB, columnId) : dir }该函数的核心逻辑:
- 读取排名元数据:
rowA.columnFiltersMeta[columnId]中保存了前面fuzzyFilter通过addMeta写入的itemRank。之所以能安全读取,是因为filterMeta: metaHelper<FuzzyFilterMeta>()已让columnFiltersMeta的类型带上itemRank字段。 - 比较排名:
compareItems直接比较两个RankingInfo的rank字段——rank高者排前,相等返回 0(见 packages/match-sorter-utils/src/index.ts)。 - 字母序兜底:当两者排名相等(或该列没有排名信息)时,回退到内置的
sortFn_alphanumeric字母序排序,保证排序结果稳定可预期。
注册排序函数并在列上引用
将fuzzySort注册到tableFeatures的sortFns插槽(见上文装配一节),然后在列定义中按名称引用:
{ accessorFn: row => `${row.firstName} ${row.lastName}`, id: 'fullName', header: 'Full Name', cell: info => info.getValue(), filterFn: 'fuzzy', // using our custom fuzzy filter function (registered in features) sortFn: 'fuzzy', // using our custom fuzzy sort function (registered in features) }也可以跳过注册步骤,把fuzzySort直接作为函数传给列的sortFn选项——两种方式等价,选择哪种取决于你是否需要在多处按名称引用同一个函数。
源码原理:排名体系与类型插槽
为了让方案落地更稳,这里补充两个来自仓库源码的关键原理。
排名分数体系
@tanstack/match-sorter-utils定义了一套从强到弱的匹配等级(见 packages/match-sorter-utils/src/index.ts):
| 排名常量 | 值 | 含义 |
|---|---|---|
CASE_SENSITIVE_EQUAL | 7 | 大小写敏感完全相等 |
EQUAL | 6 | 忽略大小写完全相等 |
STARTS_WITH | 5 | 以搜索词开头 |
WORD_STARTS_WITH | 4 | 单词以搜索词开头 |
CONTAINS | 3 | 包含搜索词 |
ACRONYM | 2 | 首字母缩略词匹配 |
MATCHES | 1 | 字符有序松散匹配(默认阈值) |
NO_MATCH | 0 | 不匹配 |
rankItem默认阈值为MATCHES(1),因此只要达到最松散的字符有序匹配即视为passed。比较两个条目时,compareItems按分数高低直接判序。这套设计让"过滤"与"排序"共享同一份打分结果:过滤看passed,排序看rank。
filterMeta 类型插槽如何工作
columnFiltersMeta是行对象上按列 ID 索引的元数据容器(见 packages/table-core/src/features/column-filtering/columnFilteringFeature.types.ts)。metaHelper<FuzzyFilterMeta>()的本质是在 features 对象上声明一个类型优先的filterMeta插槽,ExtractFilterMeta类型工具会优先采用该插槽类型、否则回退到全局合并的FilterMeta。这意味着:
- 自定义过滤函数在
addMeta写入的数据,能在排序函数里被类型安全地读取(columnFiltersMeta[columnId].itemRank已知类型); - 不需要
declare module全局增强,不同表格可以携带不同的元数据类型而互不冲突; - 所有类型约束都限定在
tableFeatures(...)产生的 features 对象作用域内。
完整示例与验证
仓库中的 filters-fuzzy 示例 是上述全部概念的可运行实现:
- 数据层:
makeData.ts生成 5000 行模拟数据,并提供"重新生成"与"百万行压力测试"按钮; - 交互层:全局模糊搜索框(防抖 500ms)+ 每列独立过滤输入框 + 分页控件;
- 行为细节:
useEffect中当fullName列被过滤时自动把排序切换到fullName列,从而立即体现"按模糊排名排序"的效果(见 examples/react/filters-fuzzy/src/main.tsx); - 测试保障:配套 Playwright 冒烟测试覆盖"表格正常渲染无报错"与"重新生成数据后首行内容变化"两个场景(见 examples/react/filters-fuzzy/tests/e2e/smoke.spec.ts)。
运行该示例的方式与仓库其他示例一致:在examples/react/filters-fuzzy目录下安装依赖并启动 Vite 开发服务器即可在浏览器中体验完整的模糊搜索交互。
小结
本文完整覆盖了 TanStack Table React 模糊过滤的落地路径:
- 安装
@tanstack/match-sorter-utils,理解rankItem打分 +compareItems排序的增量式设计; - 在
tableFeatures中装配columnFilteringFeature、globalFilteringFeature、rowSortingFeature及对应的行模型; - 定义携带
RankingInfo的自定义fuzzyFilter,通过addMeta写入排名信息,用filterFns与filterMeta插槽完成注册与类型绑定; - 分别接入全局过滤(
globalFilterFn: 'fuzzy')与列过滤(列定义filterFn: 'fuzzy'); - 定义
fuzzySort排序函数,用compareItems比较排名、以sortFn_alphanumeric兜底,实现"最相关结果排最前"; - 借助
filterMeta类型插槽机制,无需全局类型增强即可获得端到端类型安全。
这套方案兼顾了模糊搜索的体验与工程上的类型严谨性,可以直接复用到任何基于 TanStack Table React 构建的数据表格与数据网格中。
【免费下载链接】table🤖 Headless UI for building powerful tables & datagrids for TS/JS - React-Table, Vue-Table, Solid-Table, Svelte-Table项目地址: https://gitcode.com/gh_mirrors/ta/table
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考