TanStack Table 列可见性(Column Visibility)表级选项深度解析:enableHiding 与 onColumnVisibilityChange 完整指南
TanStack Table 列可见性(Column Visibility)表级选项深度解析:enableHiding 与 onColumnVisibilityChange 完整指南
本篇技术指南聚焦 TanStack Table(table-core 核心 + React/Vue/Solid/Svelte 等框架适配层)中列可见性(Column Visibility)特性的两个表级配置选项——enableHiding 与 onColumnVisibilityChange。它们定义于 TableOptions_ColumnVisibility 接口,是启用列隐藏/显示能力、接入受控状态(controlled state)的入口。读完本文,你将掌握这两个选项的默认行为、与 columnVisibility 状态的关系、外部 Atom 与回调两种状态管理模式,并能结合源码与示例搭建一个完整可用的"显示/隐藏列"控制面板。
接口概览:TableOptions_ColumnVisibility
该接口在仓库中的定义位于 columnVisibilityFeature.types.ts,完整签名如下:
export interface TableOptions_ColumnVisibility {
/**
* Whether to enable column hiding. Defaults to `true`.
*/
enableHiding?: boolean
/**
* Called with an updater when column visibility state changes. Pair this with
* `state.columnVisibility` when using external state; external atoms can own
* the slice without this callback.
*/
onColumnVisibilityChange?: OnChangeFn<ColumnVisibilityState>
}
接口仅包含两个可选属性,集中体现了列可见性特性的全部表级配置面:
| 属性 | 类型 | 默认值 | 作用 |
|---|---|---|---|
enableHiding |
boolean(可选) |
true |
是否允许隐藏列(表级总开关) |
onColumnVisibilityChange |
OnChangeFn<ColumnVisibilityState>(可选) |
由 makeStateUpdater 生成的内部更新器 |
列可见性状态变化时被调用的回调,配合 state.columnVisibility 实现受控模式 |
配套的状态类型 ColumnVisibilityState 定义在同一文件顶部(columnVisibilityFeature.types.ts):
export type ColumnVisibilityState = Record<string, boolean>
即一个"列 ID → 布尔值"的映射:某列 ID 的值为 false 表示该列被隐藏;值为 true 或该列 ID 不在映射中都表示该列可见。这一"缺失即可见"的语义是整个特性的核心约定,下文源码分析会反复印证。
选项一:enableHiding——控制列能否被隐藏
enableHiding 是列隐藏能力的总开关,默认值为 true,即默认情况下所有列都可以被隐藏或重新显示。当你希望某些列(例如主键、操作按钮列)始终固定可见时,有两种层级可以关闭隐藏能力。
表级全局禁用
将 enableHiding: false 传入 useTable 的选项对象,即可让所有列都无法隐藏:
const table = useTable({
features,
columns,
data,
enableHiding: false, // 全局禁止隐藏任何列
})
列级定向禁用
更常见的做法是在**列定义(ColumnDef)**上使用列级 enableHiding,只锁定特定列。列级选项定义于 ColumnDef_ColumnVisibility(源码见 columnVisibilityFeature.types.ts):
const columns = [
{
header: 'ID',
accessorKey: 'id',
enableHiding: false, // 该列禁止隐藏
},
{
header: 'Name',
accessorKey: 'name', // 未设置,默认可隐藏
},
]
组合判定逻辑:column_getCanHide
列级与表级两个开关并非二选一,而是**"与"关系**。核心实现位于 columnVisibilityFeature.utils.ts:
export function column_getCanHide(column) {
return (
(column.columnDef.enableHiding ?? true) &&
(column.table.options.enableHiding ?? true)
)
}
即:只有当列定义的 enableHiding(缺省视为 true)且表级 enableHiding(缺省视为 true)都为真时,该列才允许被隐藏。任一设置为 false,column.getCanHide() 都会返回 false。测试用例 columnVisibilityFeature.utils.test.ts 分别验证了"全局禁用"与"列级禁用"两条路径,均使 column_getCanHide 返回 false。
提示:即使某列被锁定为不可隐藏,它的 ID 仍可能出现在
columnVisibility状态映射中,但column_toggleVisibility在调用前会先检查column_getCanHide,不可隐藏的列会被直接跳过,状态不会被修改(见 utils 实现 columnVisibilityFeature.utils.ts)。
选项二:onColumnVisibilityChange——受控状态的更新回调
onColumnVisibilityChange 的完整类型为 OnChangeFn<ColumnVisibilityState>,即"接收一个更新器(updater)的函数"。更新器可以是新的状态映射,也可以是接收旧状态并返回新状态的函数:
// 直接传入新映射
onColumnVisibilityChange({ visits: false })
// 或传入函数式更新器
onColumnVisibilityChange((old) => ({ ...old, visits: false }))
该回调在以下场景被触发:
table.setColumnVisibility(updater)table.toggleAllColumnsVisible(value)- 任一列执行
column.toggleVisibility(value) table.resetColumnVisibility(defaultState)
默认行为:内部状态更新器
即使你不传该选项,列可见性依然可用。feature 在注册时通过 getDefaultTableOptions 为 onColumnVisibilityChange 提供了默认实现,见 columnVisibilityFeature.ts:
getDefaultTableOptions: (table) => {
return {
onColumnVisibilityChange: makeStateUpdater('columnVisibility', table),
}
}
makeStateUpdater('columnVisibility', table) 生成一个内部更新器,负责将新状态写回表的 state.columnVisibility 并触发相关订阅。这解释了为什么"什么都不配"也能开箱即用地隐藏/显示列——默认情况下表格自己管理这份状态。
受控模式:state.columnVisibility + onColumnVisibilityChange
当你需要自行拥有 columnVisibility 状态(例如持久化用户偏好、与表单联动、在表格外部读写),v8 风格的受控模式仍然受支持:把状态值传给 state.columnVisibility,把 setter 传给 onColumnVisibilityChange:
const [columnVisibility, setColumnVisibility] = useState<ColumnVisibilityState>({
columnId1: true,
columnId2: false, // 默认隐藏该列
columnId3: true,
})
const table = useTable({
features,
columns,
data,
state: {
columnVisibility,
},
onColumnVisibilityChange: setColumnVisibility,
})
推荐方案:外部 Atom(无需该回调)
按原文档的说明(React 指南),外部 Atom 可以直接拥有这个状态切片,从而无需 onColumnVisibilityChange 回调。外部 Atom 提供全应用范围内的细粒度订阅,表格之外的其他代码也能读写可见性状态,而无需重新渲染拥有表格的组件:
import { useCreateAtom, useSelector } from '@tanstack/react-store'
import { columnVisibilityFeature, tableFeatures, useTable } from '@tanstack/react-table'
import type { ColumnVisibilityState } from '@tanstack/react-table'
const features = tableFeatures({ columnVisibilityFeature })
const columnVisibilityAtom = useCreateAtom<ColumnVisibilityState>({
columnId1: true,
columnId2: false, // 默认隐藏该列
columnId3: true,
})
const columnVisibility = useSelector(columnVisibilityAtom) // 在任意需要的地方订阅
const table = useTable({
features,
columns,
data,
atoms: {
columnVisibility: columnVisibilityAtom,
},
})
此时可见性状态的写入经由 atoms.columnVisibility 直接落到外部 Atom,onColumnVisibilityChange 不再是必需项。这是 v9 框架适配层推荐的状态管理方式。
非受控模式:initialState 设定初始值
如果状态完全交给表格内部管理,只需用 initialState.columnVisibility 指定初始可见性:
const table = useTable({
features,
columns,
data,
initialState: {
columnVisibility: {
columnId1: true,
columnId2: false, // 首屏默认隐藏该列
columnId3: true,
},
},
})
注意:若
columnVisibility同时出现在initialState和state中,state的初始化优先,initialState会被忽略。二者只能二选一(见 React 指南 的 NOTE 提示)。
状态语义与默认状态
列可见性 feature 在注册时通过 getInitialState 注入初始状态(columnVisibilityFeature.ts),默认状态由 getDefaultColumnVisibilityState() 生成,即一个空对象 {}(实现见 columnVisibilityFeature.utils.ts)。
空对象的语义是:所有列 ID 在映射中缺失,因此所有列默认可见。测试 columnVisibilityFeature.utils.test.ts 验证了 getDefaultColumnVisibilityState() 返回 {},且 column_getIsVisible 在默认情况下返回 true。
column_getIsVisible 的判定逻辑(columnVisibilityFeature.utils.ts)还处理了两种特殊情况:
- 叶子列(leaf):读取
state.columnVisibility[column.id],缺失时回退为true; - 父/分组列(parent/group):自身没有直接状态条目,而是递归判断任一子列可见则该父列可见。测试用例"should return true if any child column is visible"验证了这一点(columnVisibilityFeature.utils.test.ts)。
配套 API 家族:Table、Column、Row 三层
TableOptions_ColumnVisibility 只是配置入口,特性启用后会为三个对象注入完整 API(类型定义见 columnVisibilityFeature.types.ts,注册逻辑见 columnVisibilityFeature.ts)。
Table 级 API
| API | 说明 |
|---|---|
getIsAllColumnsVisible() |
是否所有叶子列都可见(用于"全选"复选框的 checked) |
getIsSomeColumnsVisible() |
是否至少一个叶子列可见(用于三态控制) |
getToggleAllColumnsVisibilityHandler() |
复选框风格处理器,读取 event.target.checked 并切换全部列 |
getVisibleFlatColumns() |
当前可见的扁平列列表(含仍有可见后代的父列) |
getVisibleLeafColumns() |
当前可见的叶子列列表(行单元格与表头渲染通常用它) |
resetColumnVisibility(defaultState?) |
重置为 initialState.columnVisibility;传 true 则忽略初始状态、重置为 {} |
setColumnVisibility(updater) |
以新映射或更新器函数更新可见性状态 |
toggleAllColumnsVisible(value?) |
显示/隐藏所有可隐藏的叶子列 |
getToggleAllColumnsVisibilityHandler 的源码(columnVisibilityFeature.utils.ts)展示了它与 onColumnVisibilityChange 的联动:处理器把 event.target.checked 传给 table_toggleAllColumnsVisible,后者构建完整映射并调用 table_setColumnVisibility,最终经由 setStateSlice 路由到 onColumnVisibilityChange(默认是内部更新器,受控模式下是你传入的 setter)。
Column 级 API
| API | 说明 |
|---|---|
getCanHide() |
该列是否允许被隐藏(综合列级与表级 enableHiding) |
getIsVisible() |
该列当前是否可见 |
getToggleVisibilityHandler() |
复选框风格处理器,读取 event.target.checked 切换该列 |
toggleVisibility(value?) |
切换该列可见性;不传值时自动取反 |
完整签名见 Column_ColumnVisibility。
Row 级 API
| API | 说明 |
|---|---|
getVisibleCells() |
该行中属于可见列的单元格(启用列固定时按 start → center → end 排序) |
getVisibleCellsByColumnId() |
以列 ID 为键的可见单元格映射,隐藏列被剔除 |
渲染要点:务必使用"可见"系列 API
启用列隐藏后,渲染表头、表体与表脚时不要使用 table.getAllLeafColumns()、row.getAllCells() 这类忽略可见性的 API,而要改用 table.getVisibleLeafColumns()、row.getVisibleCells(),否则被隐藏的列仍会渲染出来。表头分组 API(table.getHeaderGroups() 等)本身已经考虑了列可见性,可放心使用(见 React 指南)。
table_getVisibleLeafColumns 与 row_getVisibleCells 的实现(columnVisibilityFeature.utils.ts)都是先取全集、再按 column_getIsVisible 过滤;row_getVisibleCells 额外处理了列固定(pinning)下的排序。两个 API 在 columnVisibilityFeature.ts 中注册了 memo 依赖(含 columnVisibility、columnOrder、columnPinning、grouping 等状态),状态变化时自动失效重算。
端到端示例:构建"显示/隐藏列"控制面板
仓库的官方示例 examples/react/column-visibility/src/main.tsx 是上述选项与 API 的完整落地,核心结构如下:
const features = tableFeatures({ columnVisibilityFeature })
const table = useTable({
features,
columns,
data,
// initialState: { columnVisibility: { visits: false } }, // 首屏隐藏某列
// atoms: { columnVisibility: columnVisibilityAtom }, // 推荐:外部 Atom 拥有状态
// state: { columnVisibility }, // 受控模式
// onColumnVisibilityChange: setColumnVisibility,
// enableHiding: false, // 全局禁止隐藏
debugTable: true,
})
// 全选开关 + 逐列开关
<table>
<thead>
{table.getHeaderGroups().map((headerGroup) => (
<tr key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<th key={header.id} colSpan={header.colSpan}>
{header.isPlaceholder ? null : <table.FlexRender header={header} />}
</th>
))}
</tr>
))}
</thead>
<tbody>
{table.getRowModel().rows.map((row) => (
<tr key={row.id}>
{row.getVisibleCells().map((cell) => (
<td key={cell.id}>
<table.FlexRender cell={cell} />
</td>
))}
</tr>
))}
</tbody>
</table>
控制面板部分将"全选"复选框绑定到 table.getIsAllColumnsVisible() 与 table.getToggleAllColumnsVisibilityHandler(),将逐列复选框绑定到 column.getIsVisible() 与 column.getToggleVisibilityHandler(),与 React 指南 中给出的模板一致。该示例同时提供了 e2e 冒烟测试 examples/react/column-visibility/tests/e2e/smoke.spec.ts,用于验证切换列可见性后的渲染结果。
提示:可见性菜单通常渲染列本身而非表头对象——被隐藏的列可能没有活动的表头上下文。建议使用稳定的文本标签(如自定义的"列 ID → 标签"映射、
columnDef.meta中的label字段,或直接使用column.id)作为开关文案(见 React 指南 的 NOTE)。
源码链路小结
将以上内容串起来,一次"隐藏某列"操作背后的完整调用链为:
- UI 触发
column.getToggleVisibilityHandler()或column.toggleVisibility(false); column_toggleVisibility校验column_getCanHide(列级与表级enableHiding与运算),并将新的可见性写入各叶子列(columnVisibilityFeature.utils.ts);table_setColumnVisibility通过setStateSlice将更新器路由到onColumnVisibilityChange(columnVisibilityFeature.utils.ts);- 默认情况下该回调是
makeStateUpdater生成的内部更新器,将状态写回表内;受控模式下则由你的onColumnVisibilityChange接收更新器并驱动state.columnVisibility;外部 Atom 模式下状态直接落到 Atom; - 状态变化使
getVisibleLeafColumns、getVisibleCells等 memo 依赖失效,重算后触发渲染,隐藏的列从表头与表体中消失。
常见误区
- 使用全集 API 渲染:
getAllLeafColumns()/getAllCells()不感知可见性,渲染时必须改用getVisibleLeafColumns()/getVisibleCells(); initialState与state同时提供columnVisibility:state优先,initialState被忽略,请二选一;- 误以为
enableHiding: false会从状态中清除该列:不可隐藏的列只是不会被toggleVisibility修改,若它已在状态映射中,仍可通过setColumnVisibility显式赋值(虽然不推荐); - 将分组列 ID 直接写入状态:可见性状态按叶子列 ID 索引,对分组列调用
toggleVisibility会扩散到其可隐藏的叶子列,而非写入分组列自身 ID(有测试用例专门验证,见 columnVisibilityFeature.utils.test.ts)。
延伸阅读
- React 框架列可见性指南(各框架均有对应指南,如 Vue、Angular、Svelte、Solid)
- Column_ColumnVisibility 接口 与 ColumnDef_ColumnVisibility 接口
- Table_ColumnVisibility 接口 与 TableState_ColumnVisibility 接口
- columnVisibilityFeature 变量文档 及各静态函数文档(如 table_setColumnVisibility、column_toggleVisibility)
- 官方示例 examples/react/column-visibility(其他框架如 Vue、Svelte 也有对应版本)