news 2026/9/21 17:16:44

TanStack Lit Table 全局过滤(Global Filtering)实战指南:从客户端筛选到服务端过滤

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
TanStack Lit Table 全局过滤(Global Filtering)实战指南:从客户端筛选到服务端过滤

TanStack Lit 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

本指南以@tanstack/lit-table的全局过滤(Global Filtering)能力为核心,完整讲解如何在 Lit 自定义元素中基于TableController接入跨列搜索:包括特性注册顺序、客户端与服务端两种过滤模式的取舍、12 个内置过滤函数的选择、全局过滤状态的读取与控制、搜索输入框的 UI 接入,以及自定义过滤函数的写法。读完本文,你将能在一个 Lit 表格组件中实现"一个输入框搜索所有列"的完整方案,并理解其底层 row model 与状态切片机制。

快速开始:全局过滤的最小配置

全局过滤(Global Filtering)是作用于所有列的过滤方式,与逐列过滤(Column Filtering)相对。在 Lit 适配器中,全局过滤依赖列过滤特性,因此特性注册有严格顺序:先注册columnFilteringFeature,再注册globalFilteringFeature。如果使用客户端过滤,还需在tableFeatures的 row model 槽位上注册filteredRowModel——row model 槽位是类型检查的,缺少它会直接编译报错。

以下是最小可运行的全局过滤配置:

import { LitElement, html } from 'lit' import { customElement, state } from 'lit/decorators.js' import { TableController, tableFeatures, columnFilteringFeature, globalFilteringFeature, createFilteredRowModel, filterFn_includesString, } from '@tanstack/lit-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 }, }) @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`...` } }

其中tableFeatures({ ... })把特性、row model 槽位和过滤函数注册表打包成一个可复用的features对象,传入每次渲染中的this.tableController.table(options)。这与仓库中 filters 示例 的做法一致——该示例同时注册了columnFilteringFeaturerowPaginationFeaturecreateFilteredRowModel()createPaginatedRowModel(),并只注册实际用到的四个过滤函数。

关于filterFns注册表:上面的配置只列出了本表用到的内置过滤函数。虽然直接展开整个内置注册表(filterFns: { ...filterFns })也能工作,但会把全部内置过滤函数打进打包产物。建议只注册实际用到的函数,或者干脆把函数直接传给globalFilterFn选项而无需任何注册。从源码看,filterFns.ts 中的完整注册表导出本身就标注为@deprecated,官方推荐逐个导入filterFn_*以减小包体积。

客户端过滤还是服务端过滤

过滤应当与排序、分页基于同一份数据集。判断依据很简单:

  • 浏览器持有完整数据集时,使用客户端过滤;
  • 浏览器只拿到一页或一个子集时,应使用服务端过滤——除非你刻意只对已加载的行做过滤。

完整的决策框架、性能因素以及多种数据操作组合的指引,参见仓库根目录的 客户端与服务端数据操作指南。

此外,客户端filteredRowModel在全局过滤输入变化时会触发页码自动重置钩子。是否重置取决于autoResetPageIndexautoResetAllmanualPagination选项的组合。若过滤是手动的且未注册(或被绕过)该 row model,全局过滤状态变化不会触发该钩子,此时需要在过滤变更处理器中手动重置服务端分页。

从实现上看,createFilteredRowModel在 createFilteredRowModel.ts 中把columnFiltersglobalFilter两个状态原子作为 memo 依赖,并在更新后通过onAfterUpdate回调触发table_autoResetPageIndex——这正是"全局过滤输入变化 → 页码自动重置"这一行为的源码依据。

手动服务端全局过滤

如果决定不使用内置的客户端全局过滤,而是自己实现服务端过滤,做法如下:

服务端全局过滤不需要filteredRowModel。传给表格的data应当已经是过滤后的数据。不过,如果你在features里注册了filteredRowModel,可以通过设置manualFiltering: true让表格跳过它:

import { TableController, tableFeatures, columnFilteringFeature, globalFilteringFeature, } from '@tanstack/lit-table' const features = tableFeatures({ columnFilteringFeature, globalFilteringFeature, }) const table = this.tableController.table({ features, data: this.data, columns, manualFiltering: true, })

注意:使用手动全局过滤时,本文后面讨论的许多选项都不会生效。当manualFilteringtrue,表格实例不会对传入的行应用任何全局过滤逻辑,而是假定行已被过滤,原样使用你传入的数据。

客户端全局过滤

使用内置客户端全局过滤时,把globalFilteringFeature(连同其前置依赖columnFilteringFeature)加入features,并把filteredRowModel加入 row model 槽位:

import { TableController, tableFeatures, columnFilteringFeature, globalFilteringFeature, createFilteredRowModel, filterFn_includesString, } from '@tanstack/lit-table' const features = tableFeatures({ columnFilteringFeature, globalFilteringFeature, filteredRowModel: createFilteredRowModel(), filterFns: { includesString: filterFn_includesString }, }) const table = this.tableController.table({ features, // other options... })

客户端过滤的执行链路在源码中非常清晰:createFilteredRowModel会取出全局过滤函数table_getGlobalFilterFn,过滤出所有column_getCanGlobalFilter返回true的叶子列,把全局过滤值通过resolveFilterValue归一化后对每一行逐一求值;只要任意一列命中即保留该行(对应row.columnFilters.__global__ = truebreak的逻辑),随后再与列过滤结果做水平合并过滤。这就是"全局过滤是 OR 语义、跨所有可过滤列"的底层实现,参见 createFilteredRowModel.ts。

选择全局过滤函数:globalFilterFn

globalFilterFn选项用于指定全局过滤使用的过滤函数。它可以是:

  • 字符串,引用注册在tableFeaturesfilterFns槽位上的内置或自定义过滤函数;
  • 函数本身,直接传入。
const table = this.tableController.table({ features, data: this.data, columns, globalFilterFn: 'includesString', // built-in filter function })

源码中的解析逻辑(见 globalFilteringFeature.utils.ts)表明:函数值直接返回;'auto'委托给默认的includesString(即table_getGlobalAutoFilterFn返回filterFn_includesString);字符串则从表格的过滤函数注册表中查找;在开发环境下查找不到会输出globalFilterFn 'xxx' is not registered的警告。

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

过滤函数语义
includesString不区分大小写的字符串包含
includesStringSensitive区分大小写的字符串包含
equalsString不区分大小写的字符串相等
equals严格相等===
weakEquals宽松相等==
arrIncludes行的数组(或字符串)值包含至少一个过滤值
arrIncludesAll行的数组值包含全部过滤值
arrIncludesSome行的数组值包含至少一个过滤值
arrHas行的标量值等于至少一个过滤值
inNumberRange闭区间[min, max]数字范围(端点归一化,反序自动交换)
between开区间 min/max(空白端点视为开区间)
betweenInclusive闭区间 min/max(空白端点视为开区间)

需要说明的是,这 12 个是文档针对全局过滤场景列出的常用集合;filterFns.ts 中的完整内置注册表还包含startsWithendsWithemptynotEmptyequalsStringSensitiveinDateRangegreaterThangreaterThanOrEqualTolessThanlessThanOrEqualTo等函数,均可按需导入并注册后用于globalFilterFn

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

全局过滤状态:读取与控制

globalFilter状态切片保存当前全局过滤值,通常是一个搜索字符串(切片类型是any,以便自定义过滤函数接收其他形状的值)。

render方法中读取时,使用table.state.globalFilter(这是经过传入tableController.table的选择器选取后的状态)。在事件处理器或其他非渲染代码中,可以用table.atoms.globalFilter.get()读取当前快照。

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

如果需要在表格之外访问全局过滤状态,推荐用外部 atom 通过atoms表格选项来"拥有"这个切片。Atom 保持细粒度订阅,过滤值可以从任意模块读取或订阅(例如用作服务端过滤的 query key),无需经过持有表格的组件:

import { createAtom } from '@tanstack/store' // create a stable atom at module scope (or in a shared store module) const globalFilterAtom = createAtom<string>('') // inside your element's render method const table = this.tableController.table({ features, // other options... atoms: { globalFilter: globalFilterAtom, // table.setGlobalFilter now updates globalFilterAtom }, }) const globalFilter = globalFilterAtom.get() // read the atom wherever you need the value

方式二:受控 state(v8 风格兼容)

v8 风格的state.globalFilteronGlobalFilterChange模式仍然受支持。它适合简单集成或迁移 v8 代码,但粒度不如外部 atom 精细。更深入的对比见 表格状态指南。

@state() private globalFilter: string = '' const table = this.tableController.table({ features, // other options... state: { globalFilter: this.globalFilter, }, onGlobalFilterChange: (updater) => { this.globalFilter = typeof updater === 'function' ? updater(this.globalFilter) : updater }, })

注意:在这个模式下,Lit 组件自身的@state()与表格状态双向联动,TableControllerhostUpdated阶段通过table_publishExternalState把外部状态发布回表格 store——这也是 Lit 适配器与 table-core 状态系统对接的核心机制,见 TableController.ts。

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

TanStack Table不会为你添加全局过滤的输入 UI,需要手动把输入框放进模板。一个典型做法是在表格上方放一个搜索输入框:用table.state.globalFilter读值,用table.setGlobalFilter更新:

html` <input type="text" .value=${String(table.state.globalFilter ?? '')} @input=${(e: InputEvent) => table.setGlobalFilter((e.target as HTMLInputElement).value)} placeholder="Search all columns..." /> `

setGlobalFilter的 updater 支持"新值"或"以旧值为参数的函数"两种形式,源码中它直接路由到onGlobalFilterChange处理器(见 globalFilteringFeature.utils.ts)。由于全局过滤值是标量,store 自身的Object.is身份判断会自动抑制无操作写入。

自定义全局过滤函数

自定义函数只需接收(row, columnId, filterValue)三个参数,返回布尔值表示该行是否应保留:

const customFilterFn = (row, columnId, filterValue) => { return // true if the row should be included in the filtered rows } const table = this.tableController.table({ features, // other options... globalFilterFn: customFilterFn, })

一个常见的思路是为全局过滤使用模糊匹配(fuzzy filtering)函数,这在 模糊过滤指南 中有专门讨论,对应的可运行示例见 filters-fuzzy 示例。

设置初始全局过滤状态

若想在表格初始化时设置全局过滤的初始值,可通过initialState传入。但如果你自己控制该切片(使用外部 atom 或响应式属性),则应改在 atom 或属性上设置起始值:

const table = this.tableController.table({ features, // other options... initialState: { globalFilter: 'search term', // if not controlling globalFilter state, set initial state here }, })

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

从源码看,globalFilteringFeaturegetInitialState为切片提供默认值undefined,并把用户传入的initialState合并到其后(globalFilteringFeature.ts)。而table.resetGlobalFilter无参数时会把状态重置为table.initialState.globalFilter的克隆,传入true则忽略初始状态、重置为undefined

禁用全局过滤

默认情况下,全局过滤对所有列启用。可以通过表格选项enableGlobalFilter对所有列禁用全局过滤,也可以设置enableFilters: false同时关闭列过滤与全局过滤。

禁用全局过滤会使对应列的column.getCanGlobalFilter返回false

const columns = [ { header: () => 'Id', accessorKey: 'id', enableGlobalFilter: false, // disable global filtering for this column }, //... ] //... const table = this.tableController.table({ features, // other options... columns, enableGlobalFilter: false, // disable global filtering for all columns })

column.getCanGlobalFilter的判定逻辑在 globalFilteringFeature.utils.ts 中一目了然:列定义上的enableGlobalFilter(默认true)、表格选项enableGlobalFilter(默认true)、表格选项enableFilters(默认true)、可选的getColumnCanGlobalFilter回调,以及该列必须存在 accessor(!!column.accessorFn),五者全部通过才返回true。此外特性默认的getColumnCanGlobalFilter还要求列值类型为字符串或数字(参见 globalFilteringFeature.ts)。

全局过滤 API 速查

以下 API 用于搭建全局过滤 UI 时非常有用:

  • table.setGlobalFilter— 设置全局过滤值。适合接入搜索输入框的@input处理器。
  • table.resetGlobalFilter— 重置全局过滤值为初始状态;传入table.resetGlobalFilter(true)则清空。
  • table.getGlobalFilterFn— 返回当前用于全局过滤的过滤函数。
  • table.getGlobalAutoFilterFn— 返回默认全局过滤函数(当前为includesString)。
  • column.getCanGlobalFilter— 返回某列是否参与全局过滤。调试哪些列会被搜索时很有用。

这些 API 由globalFilteringFeature通过assignTableAPIsassignPrototypeAPIs统一挂载到表格实例和列原型上(见 globalFilteringFeature.ts),并且均从@tanstack/lit-table的入口直接导出(该包重新导出了@tanstack/table-core的全部内容,见 index.ts)。

小结

全局过滤为 Lit 表格提供了"一个搜索框过滤所有列"的能力,其使用要点可归纳为:

  1. 特性顺序columnFilteringFeature必须在globalFilteringFeature之前注册;客户端过滤还需filteredRowModel
  2. 模式选择:完整数据集在浏览器端用客户端过滤;仅持有子集时用manualFiltering: true配合服务端过滤。
  3. 函数注册:按需导入并注册filterFn_*,避免整包filterFns打进产物;默认的globalFilterFnincludesString
  4. 状态管理:渲染中读table.state.globalFilter,事件中写table.setGlobalFilter;需要跨模块共享时优先用外部 atom。
  5. UI 自理:搜索输入框需手动添加,配合initialStateenableGlobalFilter与重置 API 即可完成完整交互闭环。

仓库中的 filters 示例(含列过滤与全局过滤的组合、5 万行数据基准与百万行压力测试按钮)和 filters-fuzzy 示例(模糊全局搜索)是进一步动手实践的起点。

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

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

电脑卡顿别急着重装!14种快速修复方案与长期优化指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/20 16:06:04

Atlas 300V推理卡部署YOLO:CANN环境搭建与性能优化指南

1. 一块"运算加速卡"引发的误会&#xff1a;Atlas 300V的真实身份先说个有意思的事。这几年后台经常收到类似"Atlas 300V 24G是不是运算加速卡"这种问题&#xff0c;回答起来其实有点微妙。你说它不是吧&#xff0c;它确实能把训练好的模型以极快的速度跑起…

作者头像 李华
网站建设 2026/9/20 16:05:45

TextMeshPro字体AB包冗余根治方案:TMP源码级优化实战

1. 项目概述&#xff1a;为什么TextMeshPro的AB包字体冗余会卡住团队交付节奏&#xff1f;TextMeshPro在Unity项目里几乎是UI文字渲染的事实标准&#xff0c;但凡做过中大型项目的人都踩过这个坑——打包出来的AssetBundle里&#xff0c;同一个字体文件被反复塞进十几个AB包&am…

作者头像 李华
网站建设 2026/9/20 15:56:08

LLVM项目深度解析:从源码结构到编译优化实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华