TanStack Table Ember 指南:模糊过滤(Fuzzy Filtering)原理与实现
2026/9/20 21:49:46 网站建设 项目流程

TanStack Table Ember 指南:模糊过滤(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

本文以 TanStack Table 的 Ember 集成(@tanstack/ember-table)为主线,完整讲解模糊过滤(fuzzy filtering)的配置方法、底层实现与实战技巧。你将学会如何在 Ember(Glimmer)组件中注册自定义fuzzy过滤/排序函数,结合@tanstack/match-sorter-utils实现基于近似匹配的全局过滤与列过滤,并通过排序让最接近的匹配结果置顶。全文配套可运行示例位于 examples/ember/filters-fuzzy,可作为直接参考的完整实现。

什么是模糊过滤

模糊过滤是一种基于近似匹配(approximate match)的过滤技术:它允许你搜索与给定值“相似”而非“完全相等”的数据。典型场景包括:

  • 输入带拼写错误的搜索词(如搜索jhn仍能命中John);
  • 按名称、地址等非结构化文本模糊检索;
  • 希望搜索结果按相关度排序,让最接近的匹配排在最前面。

TanStack Table 本身并不内置模糊匹配算法,而是通过自定义FilterFn接入。官方推荐使用@tanstack/match-sorter-utils库——它是 match-sorter 的分支,由 Kent C. Dodds 编写,为适配 TanStack Table逐行过滤(row by row filtering)的工作方式做了改进:原 match-sorter 在单次遍历中同时完成过滤与排序,而 match-sorter-utils 将**打分(rank)比较(compare)**拆分为低层工具,可增量地嵌入表格的过滤/排序管线。

[!NOTE] 使用模糊过滤前,需要先安装@tanstack/match-sorter-utils

pnpm add @tanstack/match-sorter-utils

该库是可选的;你也可以编写自己的过滤函数。但 match-sorter-utils 提供的rankItem既能过滤,又能返回排序所需的RankingInfo,从而让行按与搜索词的距离排序。

打分模型与匹配等级

rankItem的核心是给候选字符串与搜索词之间打出一个匹配分。打分依据定义在 packages/match-sorter-utils/src/index.ts 的rankings常量中,按匹配强度从高到低排列:

等级含义
CASE_SENSITIVE_EQUAL区分大小写的完全相等7
EQUAL不区分大小写的完全相等6
STARTS_WITH以搜索词开头5
WORD_STARTS_WITH以某个单词开头4
CONTAINS包含搜索词3
ACRONYM匹配首字母缩写2
MATCHES字符按顺序散落匹配(模糊匹配下限)1
NO_MATCH不匹配0

从 getMatchRanking 的实现可以看到匹配的判定顺序:先比较长度与大小写、再做小写化后的相等判断、前缀匹配、单词开头匹配、包含匹配,最后通过getClosenessRanking计算字符按顺序出现且间距越近分数越高的散落匹配。默认thresholdrankings.MATCHES(见 rankItem),即只要达到“按顺序散落匹配”即视为通过。该实现还通过prepareValueForComparison内置了去音标(diacritics)处理,让英文重音字符也能正常匹配。

模糊过滤的接入配置

安装特性与行模型

模糊过滤通常需要**列过滤(column filtering)全局过滤(global filtering)**两个特性,若同时启用客户端模糊排序,还需配置filteredRowModelsortedRowModel(行模型槽位是类型检查的,必须在特性之后声明):

import { useTable, tableFeatures, columnFilteringFeature, globalFilteringFeature, rowSortingFeature, createFilteredRowModel, createSortedRowModel, metaHelper, } from '@tanstack/ember-table' const features = tableFeatures({ columnFilteringFeature, globalFilteringFeature, rowSortingFeature, filteredRowModel: createFilteredRowModel(), // 客户端过滤(服务端过滤请改用 manualFiltering: true) // manualFiltering: true, // 手动服务端过滤 sortedRowModel: createSortedRowModel(), // 客户端排序(服务端排序请改用 manualSorting: true) // manualSorting: true, // 手动服务端排序 filterFns: { fuzzy: fuzzyFilter }, sortFns: { fuzzy: fuzzySort }, filterMeta: metaHelper<FuzzyFilterMeta>(), }) // 在 Glimmer 组件内 table = useTable(() => ({ features, columns, data: this.data, }))

[!NOTE] 上面的filterFnssortFns注册表只列出了本指南用到的自定义fuzzy函数。也可以展开整个内置注册表(filterFns: { ...filterFns, fuzzy: fuzzyFilter }),但这会把所有内置函数打进你的 bundle。只注册你用到的函数,或者干脆不注册、直接在列的filterFn/sortFn选项中传入函数引用。

为什么需要filterMeta

模糊排序依赖“行的匹配分数”,而分数在过滤阶段产生。filterMeta槽位(通过metaHelper<FuzzyFilterMeta>()创建)用来在过滤时把RankingInfo暂存到行上,供排序阶段复用。从源码看,columnFiltersMetacreateFilteredRowModel在过滤行模型构建时挂到每行上的元数据存储(见 packages/table-core/src/features/column-filtering/columnFilteringFeature.ts),排序函数正是通过row.columnFiltersMeta[columnId]?.itemRank读取这份数据。

在 Ember 集成中,useTable会通过 use-table.ts 用constructTable构造核心表格,并把用户传入的features合并到coreReactivityFeature之上。所有特性、注册表、行模型均在特性对象作用域内生效,因此无需任何declare module模块扩充——filterFnsfilterMeta只对使用该features对象创建的表生效。

定义自定义模糊过滤函数

FilterFn接收rowcolumnId与过滤值,返回布尔值决定该行是否保留。以下实现先用rankItem打分,把结果存入行过滤元数据,再依据itemRank.passed决定去留:

import { rankItem } from '@tanstack/match-sorter-utils' import type { RankingInfo } from '@tanstack/match-sorter-utils' import type { FilterFn, TableFeatures } from '@tanstack/ember-table' interface FuzzyFilterMeta { itemRank?: RankingInfo } // 携带 filterMeta 形态的特性类型 type FuzzyFeatures = TableFeatures & { filterMeta: FuzzyFilterMeta } const fuzzyFilter: FilterFn<FuzzyFeatures, Person> = ( row, columnId, value, addMeta, ) => { // 对单元格值打分 const itemRank = rankItem(row.getValue(columnId), value as string) // 把打分结果存入过滤元数据 addMeta?.({ itemRank }) // 返回该行是否通过过滤 return itemRank.passed }

要点说明:

  • rankItemrow.getValue(columnId)的值与搜索词(value,此处断言为字符串)进行近似打分;
  • addMeta是可选回调,因此用可选链addMeta?.({ itemRank })调用;它把itemRank挂到row.columnFiltersMeta[columnId]上,供排序复用;
  • value as string是类型断言:模糊匹配假定过滤值为字符串。

注册到特性对象

要让过滤函数能以字符串名'fuzzy'被引用,并且让存储的filterMeta得到类型检查,需要在tableFeatures中同时注册filterFnsfilterMeta两个槽位:

import { tableFeatures, metaHelper } from '@tanstack/ember-table' const features = tableFeatures({ columnFilteringFeature, globalFilteringFeature, rowSortingFeature, filteredRowModel: createFilteredRowModel(), sortedRowModel: createSortedRowModel(), filterFns: { fuzzy: fuzzyFilter }, sortFns: { fuzzy: fuzzySort }, filterMeta: metaHelper<FuzzyFilterMeta>(), })

不需要declare module扩充。filterFnsfilterMeta槽位都作用域于该features对象,只影响用它创建的表。

与全局过滤(Global Filtering)配合使用

模糊过滤最常见的用法是配合全局过滤:一个搜索框过滤所有列。做法是在tableFeaturesfilterFns槽位注册fuzzyFilter,然后在表格的globalFilterFn选项中引用字符串名'fuzzy'

import { useTable, tableFeatures, columnFilteringFeature, globalFilteringFeature, rowSortingFeature, createFilteredRowModel, createSortedRowModel, metaHelper, } from '@tanstack/ember-table' const features = tableFeatures({ columnFilteringFeature, globalFilteringFeature, rowSortingFeature, filteredRowModel: createFilteredRowModel(), sortedRowModel: createSortedRowModel(), // 需要按模糊分数排序时使用 filterFns: { fuzzy: fuzzyFilter }, sortFns: { fuzzy: fuzzySort }, filterMeta: metaHelper<FuzzyFilterMeta>(), }) // 在 Glimmer 组件内 table = useTable(() => ({ features, columns, data: this.data, globalFilterFn: 'fuzzy', }))

运行示例中,输入框通过this.table.setGlobalFilter(target.value)驱动搜索(见 examples/ember/filters-fuzzy/app/templates/application.gts),占位文案为 "Fuzzy search all columns (typos ok)...",即模糊搜索容忍拼写错误。

与列过滤(Column Filtering)配合使用

模糊过滤同样可以作用在单个列上。先按上文方式在filterFns槽位注册fuzzyFilter,然后在列定义的filterFn选项中按名称引用:

const columns = columnHelper.columns([ columnHelper.accessor((row) => `${row.firstName} ${row.lastName}`, { id: 'fullName', header: 'Full Name', cell: (info) => info.getValue(), filterFn: 'fuzzy', // 使用自定义模糊过滤函数 }), // 其他列... ])

示例中把模糊过滤应用到了组合firstNamelastName的列上。运行示例更进一步,为firstNamelastName两列均配置了filterFn: 'fuzzy'(见 application.gts)。

基于匹配分数的模糊排序

列过滤下若还想按“与搜索词的相关度”排序,可以定义自定义排序函数:优先比较两行的itemRank,分数相等时回退到字母序:

import { compareItems } from '@tanstack/match-sorter-utils' import { sortFn_alphanumeric } from '@tanstack/ember-table' import type { SortFn } from '@tanstack/ember-table' const fuzzySort: SortFn<FuzzyFeatures, Person> = (rowA, rowB, columnId) => { let dir = 0 // 仅当列存在打分信息时按分数排序 const rankA = rowA.columnFiltersMeta[columnId]?.itemRank const rankB = rowB.columnFiltersMeta[columnId]?.itemRank if (rankA && rankB) { dir = compareItems(rankA, rankB) } // 分数相同时回退到字母序 return dir === 0 ? sortFn_alphanumeric(rowA, rowB, columnId) : dir }

compareItems的实现见 packages/match-sorter-utils/src/index.ts:rank高者排前,相等返回 0。这正是把 match-sorter-utils 拆分为“打分”与“比较”两步的原因——过滤阶段打的分,排序阶段直接复用。

注册fuzzySorttableFeaturessortFns槽位(如上文配置),再在列定义中按名称引用:

columnHelper.accessor((row) => `${row.firstName} ${row.lastName}`, { id: 'fullName', header: 'Full Name', cell: (info) => info.getValue(), filterFn: 'fuzzy', // 使用自定义模糊过滤函数(注册在 features 中) sortFn: 'fuzzy', // 使用自定义模糊排序函数(注册在 features 中) })

也可以把fuzzySort作为函数直接传给列的sortFn选项(此时无需注册)。

完整可运行示例解读

仓库提供了完整的 Ember 模糊过滤示例:examples/ember/filters-fuzzy。其核心组件 application.gts 展示了生产级用法,值得逐段对照:

  • 特性组合:除columnFilteringFeatureglobalFilteringFeaturerowSortingFeature外,还叠加了rowPaginationFeature,并注册了includesStringinNumberRangefuzzy三种过滤函数以及alphanumerictextfuzzy三种排序函数(见 application.gts),示范了“只注册用到的函数”的打包友好实践;
  • 表格选项globalFilterFn: 'fuzzy'启用全局模糊搜索;注释中给出initialStateatoms(外部状态所有权)、受控state+ 回调、manualFilteringfilterFromLeafRowsmaxLeafRowFilterDepthgetColumnCanGlobalFilter等多种可选配置(见 application.gts),可对照 docs/framework/ember/guide/global-filtering.md 与 docs/framework/ember/guide/column-filtering.md 深入理解;
  • 数据规模:默认 2,000 行,点击 "Stress Test (1M rows)" 可加载 100 万行验证模糊过滤性能(数据由 faker 生成,见 make-data.ts);
  • 模板渲染:通过<FlexRenderHeader><FlexRenderCell>渲染表头与单元格,并配套分页控制、排序指示符(🔼/🔽)与实时 table state 展示,构成完整的交互式演示。

运行该示例:

# 在仓库根目录使用 pnpm(monorepo) pnpm install cd examples/ember/filters-fuzzy pnpm dev

使用建议与注意事项

  • bundle 体积:优先按需注册filterFns/sortFns,而不是展开整个内置注册表;不注册时也可直接传函数引用到列选项。
  • 服务端过滤:若数据量极大、需服务端过滤,启用manualFiltering: truemanualSorting: true同理),此时无需createFilteredRowModel/createSortedRowModel,模糊打分逻辑可在服务端实现。
  • 过滤方向filterFromLeafRowsmaxLeafRowFilterDepth控制嵌套行(subRows)的过滤传播方式,示例数据 make-data.ts 已预留subRows字段,可自行验证。
  • 类型安全FuzzyFeatures = TableFeatures & { filterMeta: FuzzyFilterMeta }filterMeta形态贯穿到FilterFnSortFn,确保row.columnFiltersMeta[columnId]?.itemRank的类型可被推断,这正是metaHelper槽位的作用。
  • 匹配下限:默认阈值是rankings.MATCHES;若希望更严格(如要求至少“包含”级别),可在rankItem中通过threshold选项调整,但示例与过滤器实现均未设置,保持默认即可。

延伸阅读

  • 全局过滤指南:全局过滤的通用配置与globalFilterFn说明;
  • 列过滤指南:列级filterFn与过滤元数据细节;
  • 排序指南:sortFnsortedRowModel的深入用法;
  • match-sorter-utils 源码:rankItemcompareItems与匹配等级的全部实现;
  • Ember 集成入口:@tanstack/ember-table导出的全部 API(useTabletableFeaturesmetaHelperFlexRenderCell等)。

【免费下载链接】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),仅供参考

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

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

立即咨询