TanStack Angular Table 全局过滤(Global Filtering)完整指南:特性注册、状态管理与服务端/客户端方案
2026/9/20 23:44:47 网站建设 项目流程

TanStack Angular Table 全局过滤(Global 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

全局过滤(Global Filtering)是 TanStack Table 中跨所有列执行统一搜索的核心能力,本指南以@tanstack/angular-table在 Angular 环境下的官方文档(docs/framework/angular/guide/global-filtering.md)为骨架,结合仓库内table-core的过滤实现源码与 Angular 官方示例,系统讲解全局过滤的完整接入流程。读完本文,你将掌握:如何注册过滤特性并正确启用客户端/服务端过滤、如何使用内置与自定义globalFilterFn、如何通过外部 Atom 或 Angular signal 掌控过滤状态、如何把搜索输入框接入表格,以及禁用与排查全局过滤的 API 用法。

前置准备:注册过滤相关特性

全局过滤在特性层面依赖列过滤(Column Filtering),因此必须先注册columnFilteringFeature,再注册globalFilteringFeature。若使用客户端过滤,还需在特性之后挂载filteredRowModel: createFilteredRowModel()——因为 row model 槽位是类型检查的,顺序错误会导致类型错误。整个特性配置通过tableFeatures聚合器传入injectTable

import { signal } from '@angular/core' import { injectTable, tableFeatures, columnFilteringFeature, globalFilteringFeature, createFilteredRowModel, filterFn_includesString, } from '@tanstack/angular-table' const features = tableFeatures({ columnFilteringFeature, globalFilteringFeature, filteredRowModel: createFilteredRowModel(), // if using client-side filtering // manualFiltering: true, // if using manual server-side filtering filterFns: { includesString: filterFn_includesString }, }) export class App { readonly data = signal(defaultData) readonly table = injectTable(() => ({ features, columns, data: this.data(), })) }

关于filterFns注册表的打包优化

上面的filterFns注册表只列出了该表实际用到的内置过滤函数。虽然也可以直接展开整个内置注册表(filterFns: { ...filterFns }),但这样做会把所有内置过滤函数打进产物包,破坏 tree-shaking。从源码 filterFns.ts 可以看到,内置注册表filterFns对象本身带有@deprecated标注,官方建议按需引入单个filterFn_*函数并只注册用到的那些;或者干脆不注册,直接把函数传给globalFilterFn/ 列级filterFn选项。

需要说明的是,table-core的完整内置注册表实际包含 18 个过滤函数(除了下文的 12 个之外,还有emptynotEmptystartsWithendsWithequalsStringSensitiveinDateRange等),官方指南中列出的 12 个是全局过滤最常用的内置函数。

injectTable的响应式工作方式

injectTable接受一个 options 工厂函数,injectTable.ts 中将它包进computed,每当工厂内部读取的任何 signal 变化时,就会重新求值并通过setOptions同步到表格实例,这正是 Angular 适配器与响应式模型保持同步的机制。因此建议把columns、特性配置、row model 等昂贵/静态的值放在工厂函数外部保持稳定引用,只在工厂内部读取data()、过滤/分页/排序等响应式状态。

客户端过滤 vs 服务端过滤:先做决策

过滤应当与排序、分页作用于同一份数据集。TanStack Table 官方给出的决策框架是:

  • 客户端过滤:浏览器持有完整数据集时使用。过滤、排序、分页全部在本地完成,交互即时。
  • 服务端过滤:浏览器只持有单页或其他子集时使用,此时数据应由服务端按过滤条件下发。除非你有意只过滤当前已加载的行,否则不应在子集上做客户端过滤。

完整的决策框架、性能考量以及数据操作组合建议,参见 客户端 vs 服务端指南。

客户端过滤的行模型与页码自动重置

客户端过滤行模型createFilteredRowModel在全局过滤输入变化时还会触发页码自动重置钩子(page-index auto-reset hook)。从 createFilteredRowModel.ts 的源码可以看到,它把columnFiltersglobalFilter两个状态切片作为 memo 依赖,并在更新后通过onAfterUpdate: skipFirstRun(() => table_autoResetPageIndex(table))调用table_autoResetPageIndex

页码是否真的重置,取决于autoResetPageIndexautoResetAllmanualPagination三个选项的取值。如果过滤是手动的(manual)且这个行模型被省略或绕过,全局过滤状态变化就不会触发该钩子,此时如果使用服务端分页,需要在过滤变化的处理函数里手动重置分页。

手动服务端全局过滤(Manual Server-Side)

如果你决定用服务端全局过滤取代内置的客户端过滤,做法如下:

  • 不需要filteredRowModel。传给表格的data应当是服务端已经过滤好的数据。
  • 但如果你在 features 中已经添加了filteredRowModel,可以通过把manualFiltering选项设为true让表格跳过它:
import { injectTable, tableFeatures, columnFilteringFeature, globalFilteringFeature, } from '@tanstack/angular-table' const features = tableFeatures({ columnFilteringFeature, globalFilteringFeature }) readonly table = injectTable(() => ({ features, data, columns, manualFiltering: true, }))

[!NOTE] 使用手动全局过滤时,本指南后续讨论的许多选项将不起作用。当manualFilteringtrue,表格实例不会对传入的行应用任何全局过滤逻辑,而是假定行已被过滤,按传入的data原样使用。

在服务端方案下,globalFilter状态本身仍然很有价值——它可以作为请求参数或查询 key 的一部分,把搜索词发送给后端,详见下文"全局过滤状态"一节。

客户端全局过滤(Client-Side)

使用内置的客户端全局过滤时,在 features 中加入globalFilteringFeature(以及它必需的columnFilteringFeature前置依赖)和filteredRowModel工厂即可:

import { injectTable, tableFeatures, columnFilteringFeature, globalFilteringFeature, createFilteredRowModel, filterFn_includesString, } from '@tanstack/angular-table' const features = tableFeatures({ columnFilteringFeature, globalFilteringFeature, filteredRowModel: createFilteredRowModel(), filterFns: { includesString: filterFn_includesString }, }) readonly table = injectTable(() => ({ features, // other options... }))

底层过滤流程

从 createFilteredRowModel.ts 的实现可以看到客户端全局过滤的完整链路:

  1. 空过滤值(undefined/null/'')会整体跳过过滤,直接返回预过滤行模型;
  2. 解析列过滤与全局过滤函数,其中全局过滤函数通过table_getGlobalFilterFn(table)解析(函数直接返回、'auto'委托给includesString、字符串从filterFns注册表查找,见 globalFilteringFeature.utils.ts);
  3. column_getCanGlobalFilter筛出所有可全局过滤的叶子列,对每一列都执行一次全局过滤函数,并给行打上__global__标记——任意一列命中即为保留行(命中即break);
  4. 最后通过filterRows综合列过滤与全局过滤结果,横向过滤每一行。

这就是"全局过滤 = 一个搜索词作用于所有允许过滤的列"的实现本质。

全局过滤函数:globalFilterFn

globalFilterFn选项决定全局过滤所用的过滤函数。它可以是一个字符串,引用注册在tableFeaturesfilterFns槽位中的内置或自定义过滤函数;也可以直接传一个函数

readonly table = injectTable(() => ({ features, // filteredRowModel and filterFns are registered in features data, columns, globalFilterFn: 'includesString', // built-in filter function }))

12 个内置过滤函数

默认情况下有 12 个内置过滤函数可选:

函数名行为说明
includesString不区分大小写的字符串包含匹配(全局过滤的默认值)
includesStringSensitive区分大小写的字符串包含匹配
equalsString不区分大小写的字符串相等
equals严格相等===
weakEquals宽松相等==
arrIncludes行的数组(或字符串)值包含过滤值中的至少一个
arrIncludesAll行的数组值包含每一个过滤值
arrIncludesSome行的数组值包含过滤值中的至少一个
arrHas行的标量值等于过滤值中的至少一个
inNumberRange闭区间[min, max]数字范围(端点会归一化,若反向则自动交换)
between开区间 min/max 范围(空端点视为开放)
betweenInclusive闭区间 min/max 范围(空端点视为开放)

以默认的includesString为例,filterFns.ts 中它的实现通过resolveFilterValue/resolveDataValue把过滤值与单元格数据统一String(...).toLowerCase()后再做includes比较——这就是"不区分大小写"的来源。inNumberRange则会先把端点parseFloat归一化,反向端点自动交换,并且明确拒绝非数字行值(null''、布尔值等不会因 JS 宽松关系强转而混入区间)。

你也可以定义自己的自定义全局过滤函数并直接传给globalFilterFn选项,详见下文"自定义全局过滤函数"。

全局过滤状态(Global Filter State)

globalFilter状态切片保存当前的全局过滤值,通常是一个搜索字符串(切片类型为any,以便自定义全局过滤函数接受其他形状的值)。在 Angular 中,读取方式是table.atoms.globalFilter.get()——表格的 atom 读取就是 signal 读取,在模板表达式、computed(...)effect(...)中读取会自动追踪更新。

方式一:外部 Atom(v9 推荐)

如果需要在表格之外访问全局过滤状态,官方推荐在 v9 中使用外部 Atom:用@tanstack/angular-storecreateAtom创建,并通过atoms表格选项传入。Atom 保留细粒度的订阅,过滤值可以被其他位置(例如服务端过滤的查询 key)直接使用,而无需在每次变化时重新执行injectTable的 options 初始化器:

import { createAtom } from '@tanstack/angular-store' export class App { readonly globalFilterAtom = createAtom<string>('') readonly table = injectTable(() => ({ features, // filteredRowModel and filterFns are registered in features // other options... atoms: { globalFilter: this.globalFilterAtom, // table.setGlobalFilter now updates globalFilterAtom }, })) // read the atom wherever you need the value (e.g. for a query key) // this.globalFilterAtom.get() }

注意:一旦通过atoms.globalFilter传入外部 Atom,table.setGlobalFilter更新的是这个 Atom 的值,外部即可实时读取。

方式二:v8 风格受控状态(Angular signal)

v8 风格的state.globalFilter+onGlobalFilterChange模式仍然受支持。在 Angular 中这意味着用一个 Angular signal 来持有切片,如下面 Basic External State 示例 所示:

readonly globalFilter = signal<string>('') readonly table = injectTable(() => ({ features, // filteredRowModel and filterFns are registered in features // other options... state: { globalFilter: this.globalFilter(), }, onGlobalFilterChange: (updater) => typeof updater === 'function' ? this.globalFilter.update(updater) : this.globalFilter.set(updater), }))

这种模式适合简单集成或从 v8 迁移的代码,但它不如外部 Atom 细粒度。两者的深入对比参见 表格状态指南。

在 UI 中添加全局过滤输入框

TanStack Table不会自动渲染全局过滤输入框,需要手动在 UI 中添加入口。典型做法是在表格上方放一个搜索输入框:用table.atoms.globalFilter.get()响应式读取当前值,用table.setGlobalFilter更新它:

<input type="text" [value]="table.atoms.globalFilter.get() ?? ''" (input)="table.setGlobalFilter($any($event.target).value)" placeholder="Search all columns..." />

更完整的实战形态可以参考仓库里的 filters-fuzzy 示例模板:它使用了一个debouncedInput指令对输入做 500ms 防抖,再通过(changeEvent)回调调用table.setGlobalFilter,配合@for渲染表头与行,是全局过滤 + 分页 + 排序组合的完整样板。对应的组件逻辑见 filters-fuzzy 示例入口。

自定义全局过滤函数

如果需要自定义全局过滤逻辑,定义一个过滤函数并传给globalFilterFn选项即可。函数签名接收(row, columnId, filterValue),返回布尔值表示该行是否应保留:

const customFilterFn = (row, columnId, filterValue) => { return // true if the row should be included in the filtered rows } readonly table = injectTable(() => ({ features, // filteredRowModel and filterFns are registered in features // other options... globalFilterFn: customFilterFn, }))

[!NOTE] 一个非常流行的做法是用模糊匹配(fuzzy)函数做全局过滤,这已在 模糊过滤指南 中完整讨论。

仓库的 filters-fuzzy 示例 就是现成范例:它借助@tanstack/match-sorter-utilsrankItem实现fuzzyFilter(通过addMetaRankingInfo写入行的columnFiltersMeta,再返回itemRank.passed),将其注册到filterFns: { fuzzy: fuzzyFilter },并通过globalFilterFn: 'fuzzy'启用;同时用compareItems实现fuzzySort,让命中行按匹配度排序。可对照 模糊过滤指南 阅读其完整代码。

初始全局过滤状态(Initial State)

如果希望在表格初始化时就带有一个全局过滤值,可以把该状态放进initialState选项。但如果你自己掌控了这个切片(外部 Atom 或 Angular signal),就应该在 Atom/signal 上设置起始值,而不是initialState

readonly table = injectTable(() => ({ features, // filteredRowModel and filterFns are registered in features // other options... initialState: { globalFilter: 'search term', // if not controlling globalFilter state, set initial state here }, }))

[!NOTE]不要同时使用initialState.globalFilter和受控的globalFilter(通过atomsstate),因为受控值会覆盖initialState.globalFilter

禁用全局过滤

默认情况下,所有列都参与全局过滤。可以通过以下方式控制:

  • 列级:在列定义中设置enableGlobalFilter: false,仅关闭该列的全局过滤;
  • 表级:在表格选项中设置enableGlobalFilter: false,关闭所有列的全局过滤;
  • 总开关:把enableFilters设为false,同时关闭列过滤与全局过滤。
const columns = [ { header: () => 'Id', accessorKey: 'id', enableGlobalFilter: false, // disable global filtering for this column }, //... ] //... readonly table = injectTable(() => ({ features, // filteredRowModel and filterFns are registered in features // other options... columns, enableGlobalFilter: false, // disable global filtering for all columns }))

禁用全局过滤后,该列的column.getCanGlobalFilterAPI 会返回false

getCanGlobalFilter的判定逻辑

从源码 globalFilteringFeature.ts 与 globalFilteringFeature.utils.ts 可以看到,一个列参与全局过滤需要同时满足以下条件:

  1. 列定义enableGlobalFilter默认true(未被列级关闭);
  2. 表格选项enableGlobalFilter默认true(未被表级关闭);
  3. 表格选项enableFilters默认true(未被总开关关闭);
  4. 可选的getColumnCanGlobalFilter回调返回true
  5. 该列存在accessorFn(即列可取值)。

默认的getColumnCanGlobalFilter还会检查列首行值是否为stringnumber类型——这解释了为什么对象值或undefined列默认不参与全局过滤。示例 filters-fuzzy 入口 的注释里也给出了getColumnCanGlobalFilter: column => column.id !== 'status'这种把单列排除在全局过滤之外的用法。

全局过滤 API 速查

以下 API 在接入全局过滤 UI 时最常用:

API作用
table.setGlobalFilter设置全局过滤值,适合接到搜索输入框的input事件处理器
table.resetGlobalFilter把全局过滤值重置为初始状态;传truetable.resetGlobalFilter(true))则忽略初始状态、清空为undefined
table.getGlobalFilterFn返回当前实际使用的全局过滤函数(用户自定义或自动解析后的结果)
table.getGlobalAutoFilterFn返回默认的全局过滤函数(目前是includesString
column.getCanGlobalFilter返回该列是否参与全局过滤,常用于调试哪些列会被搜索

其中setGlobalFilterresetGlobalFilter的实现细节也值得了解:table_setGlobalFilter直接把 updater 交给onGlobalFilterChange(globalFilteringFeature.utils.ts),updater 既可以是新值也可以是"接收旧值返回新值"的函数,Angular 适配器会把它路由到表格自己的状态管理器或你传入的外部 Atom / signal;resetGlobalFilter在默认情况下cloneState(table.initialState.globalFilter),传true则重置为undefined。当globalFilterFn指定了字符串但该函数未注册时,开发环境下会输出globalFilterFn 'xxx' is not registered的警告,便于排查拼写或注册遗漏。

组合建议与调试提示

  • 先注册、后引用globalFilterFn: 'includesString'这类字符串引用依赖filterFns槽位中已注册同名函数;直接传函数则无需注册,且天然利于 tree-shaking。
  • 与分页/排序协作:客户端过滤在行模型管线中位于预过滤行模型之后,过滤结果再交给排序与分页;服务端过滤则要自行处理globalFilter变化时的数据请求与分页重置(见 客户端 vs 服务端指南)。
  • 响应式接入:在 Angular 中凡是读取table.atoms.globalFilter.get()的模板表达式、computedeffect都会随过滤值自动更新;配合ChangeDetectionStrategy.OnPushFlexRender指令即可获得高性能的按需渲染。
  • 对照示例:完整可运行代码可参考 列过滤示例 与 模糊搜索示例(Angular 版本均位于examples/angular目录),前者覆盖基础列过滤,后者展示了模糊全局过滤 + 排序 + 分页的完整组合。

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

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

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

立即咨询