实战指南:从客户端筛选到服务端过滤)
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 示例 的做法一致——该示例同时注册了columnFilteringFeature、rowPaginationFeature、createFilteredRowModel()和createPaginatedRowModel()并只注册实际用到的四个过滤函数。关于filterFns注册表上面的配置只列出了本表用到的内置过滤函数。虽然直接展开整个内置注册表filterFns: { ...filterFns }也能工作但会把全部内置过滤函数打进打包产物。建议只注册实际用到的函数或者干脆把函数直接传给globalFilterFn选项而无需任何注册。从源码看filterFns.ts 中的完整注册表导出本身就标注为deprecated官方推荐逐个导入filterFn_*以减小包体积。客户端过滤还是服务端过滤过滤应当与排序、分页基于同一份数据集。判断依据很简单浏览器持有完整数据集时使用客户端过滤浏览器只拿到一页或一个子集时应使用服务端过滤——除非你刻意只对已加载的行做过滤。完整的决策框架、性能因素以及多种数据操作组合的指引参见仓库根目录的 客户端与服务端数据操作指南。此外客户端filteredRowModel在全局过滤输入变化时会触发页码自动重置钩子。是否重置取决于autoResetPageIndex、autoResetAll和manualPagination选项的组合。若过滤是手动的且未注册或被绕过该 row model全局过滤状态变化不会触发该钩子此时需要在过滤变更处理器中手动重置服务端分页。从实现上看createFilteredRowModel在 createFilteredRowModel.ts 中把columnFilters与globalFilter两个状态原子作为 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, })注意使用手动全局过滤时本文后面讨论的许多选项都不会生效。当manualFiltering为true表格实例不会对传入的行应用任何全局过滤逻辑而是假定行已被过滤原样使用你传入的数据。客户端全局过滤使用内置客户端全局过滤时把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__ true并break的逻辑随后再与列过滤结果做水平合并过滤。这就是全局过滤是 OR 语义、跨所有可过滤列的底层实现参见 createFilteredRowModel.ts。选择全局过滤函数globalFilterFnglobalFilterFn选项用于指定全局过滤使用的过滤函数。它可以是字符串引用注册在tableFeatures的filterFns槽位上的内置或自定义过滤函数函数本身直接传入。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 中的完整内置注册表还包含startsWith、endsWith、empty、notEmpty、equalsStringSensitive、inDateRange、greaterThan、greaterThanOrEqualTo、lessThan、lessThanOrEqualTo等函数均可按需导入并注册后用于globalFilterFn。你也可以自定义全局过滤函数并直接传给globalFilterFn见下文 自定义全局过滤函数。全局过滤状态读取与控制globalFilter状态切片保存当前全局过滤值通常是一个搜索字符串切片类型是any以便自定义过滤函数接收其他形状的值。在render方法中读取时使用table.state.globalFilter这是经过传入tableController.table的选择器选取后的状态。在事件处理器或其他非渲染代码中可以用table.atoms.globalFilter.get()读取当前快照。方式一外部 Atomv9 推荐如果需要在表格之外访问全局过滤状态推荐用外部 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 createAtomstring() // inside your elements 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方式二受控 statev8 风格兼容v8 风格的state.globalFilter加onGlobalFilterChange模式仍然受支持。它适合简单集成或迁移 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()与表格状态双向联动TableController在hostUpdated阶段通过table_publishExternalState把外部状态发布回表格 store——这也是 Lit 适配器与 table-core 状态系统对接的核心机制见 TableController.ts。在 UI 中添加全局过滤输入框TanStack Table不会为你添加全局过滤的输入 UI需要手动把输入框放进模板。一个典型做法是在表格上方放一个搜索输入框用table.state.globalFilter读值用table.setGlobalFilter更新html input typetext .value${String(table.state.globalFilter ?? )} input${(e: InputEvent) table.setGlobalFilter((e.target as HTMLInputElement).value)} placeholderSearch 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通过atoms或state因为受控值会覆盖initialState.globalFilter。从源码看globalFilteringFeature的getInitialState为切片提供默认值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通过assignTableAPIs与assignPrototypeAPIs统一挂载到表格实例和列原型上见 globalFilteringFeature.ts并且均从tanstack/lit-table的入口直接导出该包重新导出了tanstack/table-core的全部内容见 index.ts。小结全局过滤为 Lit 表格提供了一个搜索框过滤所有列的能力其使用要点可归纳为特性顺序columnFilteringFeature必须在globalFilteringFeature之前注册客户端过滤还需filteredRowModel。模式选择完整数据集在浏览器端用客户端过滤仅持有子集时用manualFiltering: true配合服务端过滤。函数注册按需导入并注册filterFn_*避免整包filterFns打进产物默认的globalFilterFn为includesString。状态管理渲染中读table.state.globalFilter事件中写table.setGlobalFilter需要跨模块共享时优先用外部 atom。UI 自理搜索输入框需手动添加配合initialState、enableGlobalFilter与重置 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),仅供参考