TanStack Table 列过滤实战指南:在 Lit 中使用 @tanstack/lit-table 构建单列与全局过滤

📅 发布时间:2026/9/20 22:40:17
TanStack Table 列过滤实战指南:在 Lit 中使用 @tanstack/lit-table 构建单列与全局过滤
前端UI组件【免费下载链接】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 的 Lit 适配器tanstack/lit-table 的官方列过滤文档展开系统讲解如何在一个 Lit 自定义元素中为表格启用列过滤、在客户端与服务端过滤之间做选择、管理列过滤状态以及如何利用 18 个内置过滤函数与自定义过滤函数定制每一列的匹配逻辑。读完本文你将掌握从零配置到复杂场景范围过滤、日期过滤、子行过滤、服务端手动过滤的完整实现方案并能直接参考仓库中的 Lit 过滤示例 落地到自己的项目中。示例先行如果希望直接跳到可运行实现仓库中为 Lit 适配器提供了四个与列过滤直接相关的示例推荐按下面的顺序阅读Column Filters 示例基础列过滤包含文本、范围、下拉选择、日期范围四种过滤输入Faceted Filters 示例分面过滤基于数据分布统计每个选项的可用性Bucketed Faceted Filters 示例分桶分面过滤Fuzzy Search 示例模糊搜索包含完整的自定义过滤函数注册示例。列过滤基础配置TanStack Table 把过滤分为两种口味列过滤Column Filtering与全局过滤Global Filtering。本文聚焦列过滤——它作用于单个列的 accessor 取值。全局过滤的细节可参考 全局过滤指南。TanStack Table 同时支持客户端过滤与手动服务端过滤。下面先看最基础的表配置把columnFilteringFeature加入 features即可启用列过滤相关的全部 API。如果你使用客户端过滤还需要在它之后注册filteredRowModel槽位——因为 row model 槽位是经过类型检查的。import { LitElement, html } from lit import { customElement, state } from lit/decorators.js import { TableController, tableFeatures, columnFilteringFeature, createFilteredRowModel, filterFn_includesString, filterFn_inNumberRange, } from tanstack/lit-table const features tableFeatures({ columnFilteringFeature, filteredRowModel: createFilteredRowModel(), // 使用客户端过滤时必须注册 // manualFiltering: true, // 使用手动服务端过滤时开启 filterFns: { includesString: filterFn_includesString, inNumberRange: filterFn_inNumberRange, }, }) customElement(my-table) class MyTable extends LitElement { state() private data defaultData private tableController new TableController(this) protected render() { const table this.tableController.table({ features, columns, data: this.data, }) return html... } }[!NOTE] 上面filterFns注册表中只列出了该表用到的内置过滤函数。虽然整体展开内置注册表filterFns: { ...filterFns }也能工作但那会把每一个内置过滤函数都打进你的产物。更优做法是只注册你实际用到的函数或者干脆不注册直接把函数作为filterFn列选项传给列定义。这一点在核心源码中有明确呼应packages/table-core/src/features/column-filtering/filterFns.ts中导出的聚合注册表filterFns对象被显式标注为deprecated其 JSDoc 说明「注册整个对象会放弃 tree-shaking所有内置过滤函数都会进入产物」并推荐改为按需导入单个filterFn_*函数见 filterFns.ts。注意该文件还额外导出了filterFn_greaterThan、filterFn_greaterThanOrEqualTo、filterFn_lessThan、filterFn_lessThanOrEqualTo四个比较型函数它们不在默认注册表内需要时可单独导入直接传给列。客户端 vs 服务端过滤先选对边界过滤应与排序、分页作用于同一份数据集。判断依据很简单客户端过滤浏览器持有完整数据集时使用服务端过滤浏览器只拿到一页或某个子集时使用除非你刻意只想过滤已加载的行。完整的决策框架、性能因素以及多数据操作组合的指导见 客户端 vs 服务端指南。从源码结构看客户端的过滤流水线是一条同步的内存转换链createFilteredRowModel工厂返回一个被tableMemo包裹的 row model 函数它的 memo 依赖是「过滤前的 row model columnFiltersatom globalFilteratom」一旦任一依赖变化就重算见 createFilteredRowModel.ts。这是 TanStack Table 始终是同步状态管理器的直接体现——真正的数据获取与后端查询发生在表格之外。另外有一个容易踩坑的细节客户端过滤 row model 会在列过滤输入变化时触发 page-index 自动重置钩子。分页索引是否重置取决于autoResetPageIndex、autoResetAll与manualPagination选项。如果过滤是手动模式且该 row model 被省略或绕过列过滤状态变化不会触发这个钩子这时你需要在过滤变更处理器里手动重置服务端分页。手动服务端过滤当你决定采用服务端过滤而不是内置的客户端过滤时做法如下手动服务端过滤不需要filteredRowModel。你传给表格的data应当已经是过滤后的结果。不过如果你的features里已经注册了filteredRowModel可以通过把manualFiltering选项设为true让表格跳过它const features tableFeatures({ columnFilteringFeature }) const table this.tableController.table({ features, data: this.data, columns, manualFiltering: true, })[!NOTE] 使用手动过滤时本指南后面讨论的很多选项都将不再生效。当manualFiltering为true表格实例不会对传入的行应用任何过滤逻辑而是假定行已被过滤直接按传入的data原样使用。在完全手动的服务端配置里客户端过滤/排序/分页 row model 被省略后其对应的页索引重置钩子也不会运行因此典型的做法是在onColumnFiltersChange等变更回调中一并重置pageIndex。完整的查询键集成模式含 TanStack Query 的useQuery与游标分页useInfiniteQuery两种形态可参考 客户端 vs 服务端指南 中的示例。客户端过滤使用内置客户端过滤时把columnFilteringFeature加入 features并在tableFeatures上以槽位形式注册filteredRowModel工厂。从tanstack/lit-table导入createFilteredRowModel与所需的过滤函数import { TableController, tableFeatures, columnFilteringFeature, createFilteredRowModel, filterFn_includesString, filterFn_inNumberRange, } from tanstack/lit-table const features tableFeatures({ columnFilteringFeature, filteredRowModel: createFilteredRowModel(), filterFns: { includesString: filterFn_includesString, inNumberRange: filterFn_inNumberRange, }, }) const table this.tableController.table({ features, data: this.data, columns, })过滤流水线内部是这样工作的见 createFilteredRowModel.ts表格遍历columnFilters数组对每个过滤项通过column_getFilterFn(column)解析出该列实际使用的过滤函数并在测试任何一行之前先对过滤值执行resolveFilterValue做一次归一化filterFn.resolveFilterValue?.(columnFilter.value) ?? columnFilter.value然后把解析后的过滤项逐个打到每一行上只有当行在所有列过滤与全局过滤上全部通过时row.columnFilters[id] ! false该行才会被保留同一文件 L182-L195。列过滤状态Column Filter State无论客户端还是服务端过滤你都可以直接使用 TanStack Table 内置的列过滤状态管理。表格与列都提供了大量 API 来变更、交互与读取过滤状态。列过滤状态被定义为对象数组形状如下interface ColumnFilter { id: string value: unknown } type ColumnFiltersState ColumnFilter[]由于它是对象数组你可以同时叠加多个列过滤。读取列过滤状态在render方法中读取时使用table.state.columnFilters这是通过tableController.table的第二个参数——选择器——挑选出的状态。TableController会把宿主订阅到table.store上因此列过滤状态一变化宿主就会自动更新。在事件处理器或其他非渲染代码中则可以用table.atoms.columnFilters.get()读取当前快照。const table this.tableController.table( { features, columns, data: this.data, //... }, (state) ({ columnFilters: state.columnFilters }), ) table.state.columnFilters // 在 render 中读取被选中的状态 table.atoms.columnFilters.get() // 在事件处理器中读取快照这与 Lit 适配器的设计完全一致LitTable类型显式区分了table.state供渲染读取的被选状态、table.atoms.slice.get()快照读取与table.subscribe细粒度订阅三种读取方式见 TableController.ts。如果你需要在表格之外也能拿到列过滤状态可以像下面这样「受控」地拥有这块状态。受控列过滤状态Controlled如果你需要在应用的其他部分方便地访问列过滤状态可以自己持有columnFilters状态切片。v9 推荐的方式是把一个外部 atom通过atoms表选项传入。Atom 保留细粒度的订阅能力过滤值可以从任意模块读取或订阅例如放进服务端过滤的 query key 中而无需经由拥有表格的组件import { createAtom } from tanstack/store import type { ColumnFiltersState } from tanstack/lit-table // 在模块作用域或共享的 store 模块中创建稳定的 atom const columnFiltersAtom createAtomColumnFiltersState([]) // 可在此设置初始列过滤状态 // 在元素的 render 方法内部 const table this.tableController.table({ features, columns, data: this.data, //... atoms: { columnFilters: columnFiltersAtom, // 表格的过滤 API 现在会更新 columnFiltersAtom }, }) const columnFilters columnFiltersAtom.get() // 在任意需要的地方读取 atom 的值另外v8 风格的state.columnFiltersonColumnFiltersChange组合仍然受支持。对于简单集成或从 v8 迁移的场景它很方便但细粒度不如外部 atom。更深入对比见 Table State 指南。state() private columnFilters: ColumnFiltersState [] //... const table this.tableController.table({ features, columns, data: this.data, //... state: { columnFilters: this.columnFilters, }, onColumnFiltersChange: (updater) { this.columnFilters typeof updater function ? updater(this.columnFilters) : updater }, })初始列过滤状态如果不需要在自己管理的状态作用域里控制列过滤只想设置一个初始过滤状态使用initialState表选项而不是stateconst table this.tableController.table({ features, columns, data: this.data, //... initialState: { columnFilters: [ { id: name, value: John, // 默认按 John 过滤 name 列 }, ], }, })[!NOTE] 不要同时使用initialState.columnFilters和state.columnFilters因为受控的state.columnFilters值会覆盖initialState.columnFilters。FilterFns每一列独有的过滤逻辑每一列都可以拥有自己的过滤逻辑。你可以从 TanStack Table 内置的过滤函数中选择也可以编写自定义函数。默认提供18 个内置过滤函数在 filterFns.ts 的注册表中对应列出函数名行为includesString大小写不敏感的字符串包含includesStringSensitive大小写敏感的字符串包含startsWith大小写不敏感的前缀匹配endsWith大小写不敏感的后缀匹配equalsString大小写不敏感的字符串相等equalsStringSensitive大小写敏感的字符串相等equals严格相等weakEquals宽松相等便于用字符串输入匹配数字行值empty行值为 nullish 或纯空白即通过过滤值充当开关标志notEmpty行值非 nullish 且非纯空白即通过过滤值充当开关标志arrIncludes行的数组或字符串值包含至少一个过滤值arrIncludesAll行的数组值包含每一个过滤值arrIncludesSome行的数组值包含至少一个过滤值arrHas行的标量值等于至少一个过滤值inNumberRange闭区间[min, max]数字范围端点归一化反序自动交换inDateRange闭区间[min, max]日期范围接受Date对象、时间戳或日期字符串空白端点为开区间between开区间 min/max 范围空白端点为开区间betweenInclusive闭区间 min/max 范围空白端点为开区间从源码看这些内置函数在比较语义上有细致的考量例如filterFn_inNumberRange会先做typeof dataValue ! number || Number.isNaN(dataValue)守卫防止null、空串、布尔值被 JavaScript 的宽松关系强制转换滑进数字区间否则null 0 null 20会意外成立见 filterFns.tsfilterFn_inDateRange则通过resolveDataValue把行值统一转成时间戳并用toDateTimestamp处理Date对象、时间戳与可解析的日期字符串同一文件 L331-L357。你也可以定义自己的自定义过滤函数要么内联作为filterFn列选项要么按名称注册到tableFeatures的filterFns槽位。自定义过滤函数[!NOTE] 这些过滤函数只在客户端过滤期间运行。无论你把自定义函数注册在filterFns槽位还是直接作为filterFn列选项传递它都应具备如下签名const myCustomFilterFn: FilterFntypeof features, MyData ( row, // Rowtypeof features, MyData columnId: string, filterValue: any, addMeta?: (meta: FilterMeta) void, ): boolean ...每个过滤函数都会收到待过滤的行row用于取出行值的columnId过滤值filterValue。并应返回true该行保留在过滤结果中或false该行被移除。const columns [ { header: () Name, accessorKey: name, filterFn: includesString, // 使用内置过滤函数 }, { header: () Age, accessorKey: age, filterFn: inNumberRange, }, { header: () Birthday, accessorKey: birthday, filterFn: myCustomFilterFn, // 引用注册在 filterFns 槽位中的自定义函数 }, { header: () Profile, accessorKey: profile, // 直接内联自定义过滤函数 filterFn: (row, columnId, filterValue) { return // 基于你的自定义逻辑返回 true 或 false }, }, ] //... const features tableFeatures({ columnFilteringFeature, filteredRowModel: createFilteredRowModel(), filterFns: { includesString: filterFn_includesString, inNumberRange: filterFn_inNumberRange, myCustomFilterFn: (row, columnId, filterValue) { return // 基于你的自定义逻辑返回 true 或 false }, startsWith: startsWithFilterFn, // 在别处定义 }, }) const table this.tableController.table({ features, columns, data: this.data, })TypeScript 说明像filterFn: myCustomFilterFn这样的字符串引用只要函数注册在tableFeatures的filterFns槽位中就会被自动类型推断。该注册表槽位取代了旧的declare module增强方式。或者你也可以完全跳过注册表直接把函数传给filterFn列选项。完整的注册示例参见 Fuzzy Search 示例。定制过滤函数行为resolveFilterValue / resolveDataValue / autoRemove你可以给过滤函数挂上几个可选属性来定制它的行为filterFn.resolveFilterValue这个挂在任意filterFn上的可选方法允许过滤函数在把过滤值传给比较逻辑之前先做转换/清洗/格式化。表格每个过滤只应用一次而非每行一次所以它也是做昂贵预处理工作的正确位置。filterFn.resolveDataValue这个可选方法在每一行的值与过滤值比较之前归一化该行值。所有用constructFilterFn辅助函数构建的过滤函数都会遵循它这包括所有内置过滤函数。filterFn.autoRemove这个可选方法接收过滤值返回true表示该过滤值应从过滤状态中移除。例如某些布尔风格过滤器可能希望在过滤值被设为false时把它从状态中移除。一旦提供这个判断就是权威的它决定保留的值即使为空字符串也会留在过滤状态中否则默认启发式规则会移除空串。而undefined的过滤值无论如何都会清除过滤。constructFilterFn辅助函数可以从一个「值级比较器」加上上述可选解析器构建出过滤函数实现见 filterFns.ts它把比较与归一化分离并将定义挂到返回函数上从而支持通过展开已有函数来派生变体const startsWithFilterFn constructFilterFn({ // 用解析后的行值与解析后的过滤值比较 filter: (dataValue, filterValue) Boolean(dataValue?.startsWith(filterValue)), // 在测试任何行之前把过滤值归一化一次 resolveFilterValue: (value) String(value).toLowerCase().trim(), // 每一行的值在进入比较器之前先归一化 resolveDataValue: (value) String(value ?? ).toLowerCase(), // 过滤值若为 falsy此处即空字符串则从过滤状态中移除 autoRemove: (value) !value, })把比较放在filter、归一化放在解析器里当你需要某个既有过滤函数的变体时非常划算。定义被挂在返回的函数上因此你可以展开任何用constructFilterFn构建的过滤函数只覆盖有差异的部分。例如一个额外忽略变音符号的includesString版本这样搜索 eric 也能命中 Éricconst normalize (value: unknown) String(value ?? ) .toLowerCase() .normalize(NFD) .replace(/\p{Diacritic}/gu, ) const includesStringIgnoreDiacritics constructFilterFn({ ...filterFn_includesString, // 复用比较器与 autoRemove 行为 resolveFilterValue: normalize, resolveDataValue: normalize, })像任何自定义过滤函数一样把该变体按名称注册进filterFns注册表或直接传给filterFn列选项即可。[!NOTE] 表格会在测试任何行之前对每个过滤应用一次resolveFilterValue。如果你在表格之外直接调用过滤函数需要自己先解析过滤值myFilterFn(row, columnId, myFilterFn.resolveFilterValue?.(rawValue) ?? rawValue)。定制列过滤行为除了过滤函数本身还有大量表格级与列级选项可以进一步定制列过滤行为。禁用列过滤默认情况下所有列都启用了列过滤。你可以通过enableColumnFilters表选项禁用全部列或enableColumnFilter列选项禁用特定列关闭它也可以用enableFilters: false表选项同时关闭列过滤与全局过滤。对某列禁用列过滤后该列的column.getCanFilterAPI 会返回false。const columns [ { header: () Id, accessorKey: id, enableColumnFilter: false, // 禁用该列的列过滤 }, //... ] //... const table this.tableController.table({ features, columns, data: this.data, enableColumnFilters: false, // 禁用所有列的列过滤 })过滤子行与展开/分组/聚合特性联动当同时使用展开expanding、分组grouping、聚合aggregation等特性时还有几个表选项可以定制列过滤对树形行的行为。从叶子行过滤filterFromLeafRows默认情况下过滤从父行向下进行如果父行被过滤掉它的所有子行也会一并被过滤掉。如果你的需求是只让用户搜索顶层行、不搜索子行这种默认行为正是你想要的——它也是性能最优的选择。但如果你希望子行也能被过滤和搜索无论父行是否被过滤掉可以把filterFromLeafRows表选项设为true。设为true后过滤将从叶子行向上进行只要某个子行或孙行被保留父行就会被包含进来。const features tableFeatures({ columnFilteringFeature, rowExpandingFeature, filteredRowModel: createFilteredRowModel(), expandedRowModel: createExpandedRowModel(), filterFns: { includesString: filterFn_includesString, inNumberRange: filterFn_inNumberRange, }, }) const table this.tableController.table({ features, columns, data: this.data, filterFromLeafRows: true, // 过滤并搜索子行 })最大叶子行过滤深度maxLeafRowFilterDepth默认情况下过滤会作用于树中所有行无论是根级父行还是父行的子叶子行。把maxLeafRowFilterDepth表选项设为0过滤将只作用于根级父行所有子行保持不过滤设为1则只过滤 1 层深的子叶子行以此类推。如果你希望父行通过过滤时保留其子行不被过滤掉用maxLeafRowFilterDepth: 0。const features tableFeatures({ columnFilteringFeature, rowExpandingFeature, filteredRowModel: createFilteredRowModel(), expandedRowModel: createExpandedRowModel(), filterFns: { includesString: filterFn_includesString, inNumberRange: filterFn_inNumberRange, }, }) const table this.tableController.table({ features, columns, data: this.data, maxLeafRowFilterDepth: 0, // 只过滤根级父行 })列过滤 API 速查有大量的 Column 与 Table API 可以用于与列过滤状态交互、并把过滤控件接到你的 UI 组件上。以下是可用 API 及其最常见的用途API用途table.setColumnFilters用新的状态整体覆盖列过滤状态table.resetColumnFilters适合做「清除全部/重置过滤」按钮column.getFilterValue获取默认初始过滤值用于输入框回填或直接把过滤值提供给过滤输入column.setFilterValue把过滤输入接到onChange/onBlur处理器上column.getCanFilter用于禁用/启用过滤输入column.getIsFiltered用于展示「该列正在被过滤」的视觉指示器column.getFilterIndex用于展示当前过滤应用的顺序column.getAutoFilterFn内部使用当列未指定过滤函数时为列查找默认过滤函数column.getFilterFn用于展示当前正在使用的过滤模式/函数端到端实战一个可运行的 Lit 过滤表仓库中的 Column Filters 示例 把上述所有概念串成了一个完整可运行的表值得逐行研读入口在 main.ts。它的关键设计如下按需注册过滤函数tableFeatures中只注册了includesString、inNumberRange、inDateRange、equalsString四个函数配合columnMeta: metaHelperMyColumnMeta()定义自定义列元数据main.ts。用 meta 驱动过滤输入形态通过meta: { filterVariant: range | select | dateRange }声明每列的过滤控件类型然后在一个独立的column-filter自定义元素里用switch渲染文本输入、数字范围、下拉选择与日期范围四种控件main.ts。其中范围类输入借助setFilterValue((old) [min, old[1]])这种函数式更新只修改区间的单端。渲染层在表头渲染中通过header.column.getCanFilter()判断是否渲染过滤控件并把header.column通过.column属性绑定传给column-filter子元素main.ts数据行通过table.getRowModel().rows遍历渲染表格同时开启了debugTable: true以便在pre中观察完整表格状态main.ts。大体积数据验证示例默认生成 5 万行数据并提供一键生成 100 万行的「Stress Test」按钮main.ts用于验证列过滤在客户端处理大数据量时的性能表现。对应的端到端测试位于 smoke.spec.ts。运行该示例的方式与仓库内其他示例一致在 examples/lit/filters 目录下安装依赖后启动 Vite 开发服务器vite.config.js已就绪即可在浏览器中交互体验列过滤全流程。小结列过滤是 TanStack Table 数据流水线过滤 → 排序 → 分页的第一环也是filteredRowModelrow model 的核心职责。本文覆盖了从「选择客户端还是服务端」到「状态管理方式非受控 / 受控 / 外部 atom」「18 个内置过滤函数 constructFilterFn派生变体」「filterFromLeafRows与maxLeafRowFilterDepth的树形行为定制」的完整链路。在动手实现前建议先通读 客户端 vs 服务端指南 确定数据边界再以 Column Filters 示例 为模板起步分面与模糊搜索场景则分别参考 filters-faceted、filters-faceted-bucketed 与 filters-fuzzy 示例。赞分享前端UI组件【免费下载链接】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 Lit Table 全局过滤Global Filtering实战指南从客户端筛选到服务端过滤TanStack Lit Table 全局过滤Global Filtering实战指南从客户端筛选到服务端过滤 本指南以 tanstack/lit ta前端UI组件K3s 如何为 btrfs 文件系统启用 containerd btrfs 快照器并验证快照生成K3s 如何为 btrfs 文件系统启用 containerd btrfs 快照器并验证快照生成 如果你的节点数据目录建在 btrfs 文件系统上K3s 内置前端UI组件tanstack/lit-table 参考指南面向 Lit 的 TanStack Table 适配层 API 全景tanstack/lit table 参考指南面向 Lit 的 TanStack Table 适配层 API 全景 tanstack/lit table前端UI组件上一篇codesandbox-client中的Svelte开发轻量级框架新体验下一篇Cursor试用限制终极解决方案开源工具go-cursor-help完整指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考