TanStack Alpine Table 列显隐(Column Visibility)功能实践指南:从状态管理到渲染集成
2026/9/19 7:49:46 网站建设 项目流程

TanStack Alpine Table 列显隐(Column Visibility)功能实践指南:从状态管理到渲染集成

【免费下载链接】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 v9 体系下 Alpine 适配层@tanstack/alpine-table的列显隐功能展开,讲解如何通过columnVisibilityFeature为表格启用列隐藏/显示能力,并结合Alpine.reactive@tanstack/store外部 atom 与受控state三种状态所有权方案,构建可持久化、可交互的列显隐 UI。读者完成本文后将掌握columnVisibility状态的语义与优先级规则、列级与表级显隐 API 的完整用法,以及在 Alpine 模板中渲染"可见性感知"表头与表体的正确方式。

本文对应的完整可运行示例位于 examples/alpine/column-visibility,其功能实现源码位于 packages/table-core/src/features/column-visibility/columnVisibilityFeature.ts。

启用列显隐功能

@tanstack/alpine-table是 TanStack Table v9 的 Alpine.js 适配层,列显隐能力由核心包@tanstack/table-core中的columnVisibilityFeature提供,并通过tableFeatures组合进表格实例。启用该 feature 后,表格会获得专用的columnVisibility状态以及一组管理显隐的 API。

import { columnVisibilityFeature, createTable, tableFeatures, } from '@tanstack/alpine-table' const features = tableFeatures({ columnVisibilityFeature }) const table = createTable({ features, columns, get data() { return local.data }, })

在 packages/table-core/src/features/column-visibility/columnVisibilityFeature.ts 中可以确认:该 feature 通过getInitialState注入默认的columnVisibility状态,通过assignColumnPrototype挂载列级 API(column_getIsVisiblecolumn_getCanHidecolumn_toggleVisibilitycolumn_getToggleVisibilityHandler),通过assignRowPrototype挂载行级 API(row_getVisibleCellsrow_getVisibleCellsByColumnId),并通过constructTableAPIs挂载表级 API(table_getVisibleFlatColumnstable_getVisibleLeafColumnstable_setColumnVisibilitytable_toggleAllColumnsVisible等)。

columnVisibility 状态语义

columnVisibility是一个"列 ID 到布尔值"的映射对象。其语义遵循"缺失即可见"的约定:

  • 列的 ID不在映射中,或对应值为true:列可见;
  • 列的 ID映射中且值为false:列隐藏。

对应实现见 columnVisibilityFeature.utils.ts 中的column_getIsVisible:叶子列直接读取atoms.columnVisibility中自身 id 的值,缺失时回退为true;父级(分组)列则递归检查其子列,只要存在任一可见子列即视为可见。因此,columnVisibility状态只以叶子列 ID 为键,对分组列做显隐操作时,框架会展开到其全部可隐藏的叶子列(见column_toggleVisibility的 leafColumns 遍历逻辑,同文件)。

默认状态为空对象(getDefaultColumnVisibilityState返回空 map),即默认所有列可见。table_resetColumnVisibility可以恢复初始状态:无参数时克隆initialState.columnVisibility,传true则重置为空对象(全部可见)。

三种状态所有权方案

按"谁拥有columnVisibility状态"划分,官方推荐三种做法,适用场景不同。

方案一:外部 Atom(v9 推荐,最细粒度)

如果需要在表格之外拥有该状态(例如持久化用户偏好、跨组件共享),v9 推荐使用外部 atom 并通过atoms选项注入。@tanstack/store本就是@tanstack/alpine-table的依赖,因此createAtom开箱即用。外部 atom 让应用中任何位置都能进行细粒度订阅,其他代码读写可见性状态时无需经过持有表格的组件。

import { createAtom } from '@tanstack/store' import { columnVisibilityFeature, createTable, tableFeatures, } from '@tanstack/alpine-table' import type { ColumnVisibilityState } from '@tanstack/alpine-table' const features = tableFeatures({ columnVisibilityFeature }) const columnVisibilityAtom = createAtom<ColumnVisibilityState>({ columnId1: true, columnId2: false, // 默认隐藏该列 columnId3: true, }) // 在任意需要的地方订阅 atom columnVisibilityAtom.subscribe(() => { // 响应显隐变化 }) const table = createTable({ features, //... atoms: { columnVisibility: columnVisibilityAtom, }, })

源码侧可以验证 atom 是显隐 API 的数据源:feature 的列级与表级方法均以table.atoms.columnVisibility?.get()作为 memo 依赖(见 columnVisibilityFeature.ts),column_getIsVisible也直接从该 atom 读取。通过columnVisibilityAtom.set(...)update(...)写入新值时,订阅方会收到通知并触发相关 memo 失效,这正是"细粒度订阅"的底层机制。

方案二:Alpine.reactive 受控 slice(v8 风格兼容)

v8 风格的state.columnVisibility+onColumnVisibilityChange组合依然受支持:将 slice 放在Alpine.reactive对象中,由你负责回写。该方案便于简单集成或迁移 v8 代码,但粒度不如外部 atom 细。更深入的对比可参考 Table State Guide。

const local = Alpine.reactive({ columnVisibility: { columnId1: true, columnId2: false, // 默认隐藏该列 columnId3: true, } as ColumnVisibilityState, }) const table = createTable({ features, //... state: { get columnVisibility() { return local.columnVisibility // 把响应式 slice 接回表格 }, //... }, onColumnVisibilityChange: (updater) => { local.columnVisibility = typeof updater === 'function' ? updater(local.columnVisibility) : updater }, })

该方案依然有效,是因为 feature 在getDefaultTableOptions中通过makeStateUpdater('columnVisibility', table)生成了默认的onColumnVisibilityChange(见 columnVisibilityFeature.ts);一旦你显式传入自己的 handler,即可接管写入逻辑。

方案三:initialState 一次性初始化

如果不需要在表格外部管理显隐状态,直接通过initialState设置初始显隐即可,之后由表格内部状态接管。

[!NOTE] 如果columnVisibility同时出现在initialState与受控选项(atomsstate)中,受控值优先,initialState会被忽略。请只在其中一个位置提供columnVisibility

const features = tableFeatures({ columnVisibilityFeature }) const table = createTable({ features, //... initialState: { columnVisibility: { columnId1: true, columnId2: false, // 默认隐藏该列 columnId3: true, }, //... }, })

在示例 examples/alpine/column-visibility/src/main.ts 中,三种方案以注释形式并列给出(initialState/atoms/state+onColumnVisibilityChange),并配有enableHiding: false(表格级禁用隐藏)与debugTable: true开关,可直接取消注释切换验证。

禁止隐藏指定列

默认所有列都可被隐藏。若要阻止某些列被隐藏,为该列设置enableHiding: false

const columns = [ { header: 'ID', accessorKey: 'id', enableHiding: false, // 禁用该列的隐藏能力 }, { header: 'Name', accessorKey: 'name', // 可以被隐藏 }, ]

显隐判断同时受列级columnDef.enableHiding与表格级table.options.enableHiding约束,两者默认值均为true,任一为false即不可隐藏,见 columnVisibilityFeature.utils.ts 的column_getCanHide。此外table_toggleAllColumnsVisible在"全部隐藏"时会将不可隐藏列的可见性写为true,保证这些列始终可见(同文件)。

列级显隐 API 与开关 UI

以下列级方法专为渲染显隐开关设计,见 columnVisibilityFeature.ts 中assignColumnPrototype的注册:

  • column.getCanHide():是否允许隐藏。适合用于禁用显隐开关(例如enableHiding: false的列)。
  • column.getIsVisible():当前是否可见。适合设置开关的初始勾选状态。
  • column.toggleVisibility(visible?):切换列可见性。省略参数时翻转当前状态;显式传布尔值时直接写入。对分组列会递归应用到可隐藏的叶子列。
  • column.getToggleVisibilityHandler():返回一个事件处理器,读取event.target.checked并写入列可见性,是toggleVisibility与 UI 事件之间的快捷接线。

在 Alpine 模板中,请将复选框渲染在真实 DOM 元素上(而非x-html字符串内),用:checked绑定column.getIsVisible(),用:disabled绑定!column.getCanHide(),并从@change调用getToggleVisibilityHandler返回的处理器:

<template x-for="column in table.getAllLeafColumns()" :key="column.id"> <label> <input type="checkbox" :checked="column.getIsVisible()" :disabled="!column.getCanHide()" @change="column.getToggleVisibilityHandler()($event)" /> <span x-text="column.id"></span> </label> </template>

"全选/全不选"开关则使用表级辅助方法table.getIsAllColumnsVisible()table.getToggleAllColumnsVisibilityHandler()

<label> <input type="checkbox" :checked="table.getIsAllColumnsVisible()" @change="table.getToggleAllColumnsVisibilityHandler()($event)" /> Toggle All </label>

对应的完整界面实现在 examples/alpine/column-visibility/index.html:左侧column-toggle-panel面板顶部是 Toggle All 复选框,下方遍历table.getAllLeafColumns()为每个叶子列渲染独立开关。这些 handler 均以event.target.checked为数据源(见column_getToggleVisibilityHandlertable_getToggleAllColumnsVisibilityHandler,columnVisibilityFeature.utils.ts 与 同文件),因此绑定的元素必须是真实输入控件。

此外,表级还提供以下编程式 API(注册于 columnVisibilityFeature.ts):

  • table.setColumnVisibility(updater):接受新状态对象或(old) => new更新函数,通过onColumnVisibilityChange路由写入;
  • table.resetColumnVisibility(defaultState?):重置可见性;
  • table.toggleAllColumnsVisible(value?):显示/隐藏全部可隐藏列;
  • table.getIsSomeColumnsVisible():是否至少有一列可见,适合实现三态"全选"控件。

渲染"可见性感知"的表头与表体

启用列显隐后,一个常见误区是继续使用不考虑可见性的 API。以下 API不会把列显隐纳入计算:

  • table.getAllLeafColumns()table.getAllFlatColumns()(会返回隐藏列);
  • row.getAllCells()(会返回隐藏列的单元格)。

应改用对应的可见性感知变体:

  • table.getVisibleLeafColumns()table.getVisibleFlatColumns():仅返回当前可见的列(实现见 columnVisibilityFeature.utils.ts);
  • row.getVisibleCells():仅返回可见列的单元格,且在启用列固定(column pinning)时按"起始固定列 → 中间列 → 结尾固定列"排序(同文件);
  • row.getVisibleCellsByColumnId():以列 ID 为键的可见单元格查找表。

表头分组 API(table.getHeaderGroups()table.getFooterGroups())则已经内置了可见性感知,无需额外处理。渲染单元格与表头内容时使用x-html="FlexRender(...)",迭代行模型时使用可见性感知 API:

<table> <thead> <template x-for="headerGroup in table.getHeaderGroups()" :key="headerGroup.id" > <tr> <!-- 表头分组已自动考虑列可见性 --> <template x-for="header in headerGroup.headers" :key="header.id"> <th :colspan="header.colSpan"> <template x-if="!header.isPlaceholder"> <span x-html="FlexRender({ header })"></span> </template> </th> </template> </tr> </template> </thead> <tbody> <template x-for="row in table.getRowModel().rows" :key="row.id"> <tr> <!-- 使用可见性感知的单元格列表 --> <template x-for="cell in row.getVisibleCells()" :key="cell.id"> <td x-html="FlexRender({ cell })"></td> </template> </tr> </template> </tbody> </table>

上述模板与官方示例 examples/alpine/column-visibility/index.html 完全一致,示例额外渲染了tfoot页脚(同样基于table.getFooterGroups(),其中header.isPlaceholder用于跳过分组占位单元格)。FlexRender来自@tanstack/alpine-table(见 flexRender.ts),负责把列定义中的header/cell/footer渲染函数输出为 HTML 字符串。

由于columnVisibility的写入经由受控通道(atom 或state+onColumnVisibilityChange)回流,row.getVisibleCells()等 API 的 memo 依赖中包含了table.atoms.columnVisibility(见 columnVisibilityFeature.ts),因此勾选开关后表体单元格会随之增删,且 Alpine 的x-for依据:key高效复用 DOM。

分组列场景下的显隐行为

示例 examples/alpine/column-visibility/src/main.ts 定义了两级分组列结构(Name 组含firstName/lastName,Info 组含age与 More Info 子组)。在分组结构下需要注意:

  • 显隐状态只记录叶子列columnVisibility中不会出现分组列 ID;
  • 对分组列调用toggleVisibility时,框架会遍历其叶子列逐个写入(见column_toggleVisibility的 leafColumns 循环);
  • 分组列的getIsVisible由子列决定:存在任一可见子列即为可见,因此"全隐藏"后整个分组列也会从表头消失,同时表头单元格的colSpan会自动适配剩余可见列。

运行与验证

示例通过 Vite 运行,package.json(examples/alpine/column-visibility/package.json)依赖@tanstack/alpine-tablealpinejs@faker-js/faker,并提供以下脚本:

pnpm install # 安装依赖 pnpm dev # 启动 Vite 开发服务器(package.json scripts.dev) pnpm build # 生产构建 pnpm test:types # tsc --noEmit 类型检查 pnpm test:e2e # Playwright 端到端测试

仓库为该示例配有 Playwright 冒烟测试 examples/alpine/column-visibility/tests/e2e/smoke.spec.ts:启动示例服务器、断言表格与表头可见、点击 "Regenerate Data" 后校验首行数据发生变化且页面无报错,覆盖了"表格渲染 + 数据响应式刷新"的核心链路。运行环境为 Alpine.js + Vite 的浏览器场景,测试配置参考仓库根目录 playwright.config.ts。

小结

@tanstack/alpine-table中启用列显隐只需三步:用tableFeatures({ columnVisibilityFeature })组合 feature、按需选择atoms(推荐)/state+onColumnVisibilityChange/initialState三种状态所有权方案之一、在模板中分别用"可见性感知"API(getVisibleLeafColumnsgetVisibleCellsgetHeaderGroups)渲染表头与表体。通过enableHiding保护关键列、通过列级与表级 toggle API 快速搭建显隐开关,即可为 Alpine 应用交付完整的列显隐交互。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询