- 前端
- UI组件
【免费下载链接】table
🤖 Headless UI for building powerful tables & datagrids for TS/JS - React-Table, Vue-Table, Solid-Table, Svelte-Table
createFacetedMinMaxValues()是 TanStack Table(本仓库 table-core 核心包)中用于**分面过滤(Faceted Filtering)**的数值范围辅助函数工厂。它接收表格与列 ID,返回一个被记忆化的读取函数,该函数根据当前分面行模型推导出该列可用数值的[min, max]区间,供范围滑块、数值输入框等过滤 UI 直接消费。读完本文,你将掌握该函数的完整签名、底层算法、记忆化机制、__global__全局分面语义,以及如何将它注册到tableFeatures并接入客户端或服务端分面场景。
函数签名与核心职责
关联文档(createFacetedMinMaxValues 函数参考)给出的完整签名如下:
function createFacetedMinMaxValues<TFeatures, TData>(): (table, columnId) => () => [number, number] | undefined;类型参数
| 类型参数 | 约束 | 默认值 | 含义 |
|---|---|---|---|
TFeatures | extendsTableFeatures | 无 | 表格功能特性集合类型 |
TData | extendsRowData | any | 行数据类型 |
返回值(柯里化工厂)
该函数是柯里化的三层结构,每次调用返回一个更具体的函数:
- 第一次调用(无参数):返回
(table, columnId) => () => [number, number] | undefined——一个针对指定表格与列的闭包工厂; - 第二次调用:传入
table: Table<TFeatures, TData>(类型见Table)与columnId: string,返回一个零参数读取函数; - 第三次调用(真正读取):返回
[number, number] | undefined——即[最小数值, 最大数值]元组;当数据为空或列中没有可转换为数值的单元格时返回undefined。
从 源码实现 看,第二层返回的读取函数通过tableMemo包装:
export function createFacetedMinMaxValues<TFeatures, TData>(): ( table: Table<TFeatures, TData>, columnId: string, ) => () => undefined | [number, number] { return (_table, columnId) => { const table = _table as unknown as Table_Internal<TFeatures, TData> return tableMemo({ feature: 'columnFacetingFeature', fn: (flatRows) => _createFacetedMinMaxValues(table, columnId, flatRows), fnName: 'table.getFacetedMinMaxValues', memoDeps: () => { /* 依赖收集 */ }, table, }) } }要点:记忆化发生在第三层——只要依赖(分面行模型的flatRows)没有变化,反复读取会直接返回缓存结果,不会重复扫描数据。
记忆化依赖:从分面行模型取数
createFacetedMinMaxValues的 memo 依赖决定了它何时重算,其逻辑在memoDeps中:
- 普通列:依赖
column.getFacetedRowModel().flatRows。分面行模型会应用"其他列的过滤条件、但排除本列自身的过滤条件",因此当其他列的过滤状态变化时,本列的范围会自动重算; __global__全局上下文:依赖table.getGlobalFacetedRowModel().flatRows,全局分面行模型会应用列过滤条件但排除全局过滤自身;- 列不存在时:回退到
table.getPreFilteredRowModel().flatRows,即使用未过滤的行。
这两条路径分别由工具函数column_getFacetedRowModel与table_getGlobalFacetedRowModel提供,实现在 columnFacetingFeature.utils.ts。该文件同时展示了分面 getter 的工厂解析与缓存模式:每个列 ID 对应的工厂函数只解析一次,缓存在table._rowModels.facetedMinMaxValues映射中;若table.options.features.facetedMinMaxValues未注册工厂,则回退为() => undefined。
底层算法:Number() 强制转换与 NaN 跳过
真正执行范围计算的是内部函数_createFacetedMinMaxValues,核心算法如下:
if (!flatRows.length) return undefined const columnIds = columnId === '__global__' ? table .getAllLeafColumns() .filter((column) => column_getCanGlobalFilter(column)) .map((column) => column.id) : [columnId] let facetedMinValue = Number.POSITIVE_INFINITY let facetedMaxValue = Number.NEGATIVE_INFINITY let foundAny = false for (let i = 0; i < flatRows.length; i++) { for (let c = 0; c < columnIds.length; c++) { const value = Number(flatRows[i]!.getValue(columnIds[c]!)) if (Number.isNaN(value)) continue foundAny = true if (value < facetedMinValue) facetedMinValue = value if (value > facetedMaxValue) facetedMaxValue = value } } if (!foundAny) return undefined return [facetedMinValue, facetedMaxValue]由此可以归纳出几个可验证的实现事实:
- 空行集:
flatRows.length === 0时直接返回undefined; Number()强制转换:单元格值一律通过Number()转换,因此数字字符串(如'40')会被当作数值参与计算,null会被转换为0;- NaN 跳过:任何
Number()结果为NaN的单元格(如非数字文本)被跳过,不影响范围; - 无有效数值:若所有单元格都转换失败(
foundAny === false),返回undefined; - 单行数据:返回
[x, x],即 min 与 max 相等; - 全局分面:当
columnId === '__global__'时,聚合范围横跨所有"可参与全局过滤"的叶子列(通过column_getCanGlobalFilter过滤),并在所有行上求全局最小/最大值。
注册与接入:tableFeatures 与 row-model 槽
要启用分面 min/max 功能,需要将相关特性与工厂注册到tableFeatures。根据官方指南 Column Faceting Guide 与核心技能文档 column-faceting SKILL,标准客户端配置如下:
import { columnFacetingFeature, columnFilteringFeature, createFacetedMinMaxValues, createFacetedRowModel, createFacetedUniqueValues, createFilteredRowModel, filterFns, tableFeatures, } from '@tanstack/table-core' export const features = tableFeatures({ columnFilteringFeature, filteredRowModel: createFilteredRowModel(), // 客户端过滤所需 filterFns, columnFacetingFeature, facetedRowModel: createFacetedRowModel(), // 分面行模型 facetedUniqueValues: createFacetedUniqueValues(), // 唯一值/计数 facetedMinMaxValues: createFacetedMinMaxValues(), // 数值范围 })常见错误提示:仅仅注册columnFacetingFeature而不注册facetedMinMaxValues等 row-model 槽,getter 将退化为返回undefined(facetedUniqueValues退化返回空Map,facetedRowModel退化返回预过滤行模型)。columnFacetingFeature本身不记忆化,而是由各个内置createFaceted*工厂内部记忆化,这一点在 columnFacetingFeature.ts 的注释中明确说明——这样做是为了避免冻结数据独立变化的自定义工厂。
公开 API 消费
注册后,即可通过列 API 与表 API 读取范围(类型定义见 columnFacetingFeature.types.ts):
// 单列范围:应用于"其他列过滤、排除本列过滤"后的行 const range = table.getColumn('age')?.getFacetedMinMaxValues() // 全局过滤范围:聚合所有可全局过滤列 const globalRange = table.getGlobalFacetedMinMaxValues() // 直接驱动范围滑块 const [min, max] = table.getColumn('age')?.getFacetedMinMaxValues() ?? [0, 1]指南文档给出了典型的范围滑块接入方式:
const [min, max] = column.getFacetedMinMaxValues() ?? [0, 1] return ( <input type="range" min={min} max={max} value={currentValue} onChange={(event) => column.setFilterValue(Number(event.target.value))} /> )注意:min/max 仅描述可供过滤 UI 选择的数值区间,行是否匹配由该列配置的 filter function 决定。
测试验证:真实实现的行为边界
仓库中的两组测试用例对上述行为提供了直接佐证:
- createFacetedRowModels.test.ts(真实实现测试)覆盖了:数字列返回
[min, max];数字字符串经Number()强转('5'/'15'→[5, 15]);NaN 值跳过('not-a-number'被忽略,返回[3, 7]);无数字列返回undefined;空数据返回undefined;单行返回[42, 42];以及Number(null)为0的既有行为([0, 40])。该文件还验证了引用稳定性:输入不变时重复读取返回同一引用,而其他列过滤变化后立即重算(如setColumnFilters后[30, 40]); - columnFacetingFeature.test.ts(单元测试)验证了工厂按列与全局上下文只解析一次(
facetedMinMaxValues被调用 2 次:'status'与'__global__'),确认了 getter 层的缓存语义。
自定义服务端分面工厂
当过滤在服务端完成时,浏览器加载的行不足以计算完整范围。此时可以自定义facetedMinMaxValues工厂:工厂接收(table, columnId),返回一个每次读取都会执行的函数(表不缓存其结果),因此应通过 signal、store 或table.options.meta读取最新服务端数据。自定义工厂同样会收到内部__global__列 ID,可按需分支:
const features = tableFeatures({ columnFacetingFeature, facetedMinMaxValues: (table, columnId) => () => { if (columnId === '__global__') { return table.options.meta?.serverFacets?.globalMinMax } return table.options.meta?.serverFacets?.minMaxValues[columnId] }, })若要匹配内置列分面语义,服务端查询同样应"应用其他列的过滤、排除本列自身的过滤",从而在当前分面中保留备选选项、并允许多个分面互相收窄。
小结
createFacetedMinMaxValues()是分面过滤体系中"数值范围"的标准化产出者:它通过柯里化工厂 +tableMemo实现按需计算与引用稳定,通过Number()强转与 NaN 跳过保证了对异构单元格的容错,通过__global__特殊列 ID 支撑全局过滤的跨列聚合。无论是客户端范围滑块,还是服务端分面元数据接入,理解其签名、注册方式与底层算法,都能帮助你准确预测它何时返回undefined、何时返回[min, max],从而写出更健壮的过滤 UI。相关代码可在 createFacetedMinMaxValues.ts(自 src/index.ts 导出)及分面特性目录packages/table-core/src/features/column-faceting/下继续深入阅读。
- 前端
- UI组件
【免费下载链接】table
🤖 Headless UI for building powerful tables & datagrids for TS/JS - React-Table, Vue-Table, Solid-Table, Svelte-Table
相关推荐
TanStack Table Ember 分面(Faceting)指南:构建多选过滤、数值范围与分桶过滤界面的完整实战
TanStack Table Ember 分面(Faceting)指南:构建多选过滤、数值范围与分桶过滤界面的完整实战 导读 分面(Faceting)是构建现代
前端UI组件TanStack Table for Preact 列分面(Column Faceting)实战指南:构建可计数、可搜索、可范围化的过滤 UI
TanStack Table for Preact 列分面(Column Faceting)实战指南:构建可计数、可搜索、可范围化的过滤 UI 列分面(Face
前端UI组件Pot跨平台翻译软件终极指南:如何轻松实现划词翻译与OCR识别
Pot跨平台翻译软件终极指南:如何轻松实现划词翻译与OCR识别 还在为阅读外文资料而烦恼吗?Pot Desktop是一款完全免费的跨平台翻译神器,集成了划词翻译
前端UI组件
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考