TanStack Table 列固定状态 ColumnPinningState 完全指南:start/end 语义、状态流转与源码级实现
TanStack Table 列固定状态 ColumnPinningState 完全指南start/end 语义、状态流转与源码级实现【免费下载链接】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/tableColumnPinningState 是 TanStack Table 中驱动列固定column pinning功能的核心状态类型它以start与end两个字符串数组记录被固定列的 ID把表格在逻辑上划分为起始区、中心区、结束区三个区域。本文以 ColumnPinningState 接口文档 为骨架结合 table-core 源码 与 示例工程 深入展开读完你将掌握该状态的精确语义、默认值与初始化方式、受控/非受控两种状态管理模式、底层固定的原子操作原理以及如何在实际表格中实现带固定列的冻结式布局。ColumnPinningState 是什么一份只有两个属性的最小状态契约在 columnPinningFeature.types.ts 中ColumnPinningState被定义为极其精简的接口export interface ColumnPinningState { start: Arraystring end: Arraystring }它只有两个属性且类型完全相同都是由列 IDcolumn id组成的字符串数组。这份文档给出的契约包含三条关键语义start: string[]—— 固定到逻辑起始区的列 ID 数组顺序即固定后的显示顺序end: string[]—— 固定到逻辑结束区的列 ID 数组顺序即固定后的显示顺序数组中未出现的列全部归属于居中的中心区center随表格容器横向滚动。理解该类型必须先理解start/end为什么叫逻辑位置而非左右。在 类型定义文件 顶部有明确的注释说明In LTR languages/layouts,startusually corresponds to left andendusually corresponds to right. In RTL languages/layouts,startusually corresponds to right andendusually corresponds to left.也就是说在 LTR从左到右布局下start≈ 左、end≈ 右而在 RTL从右到左如阿拉伯语布局下二者互换。渲染层拿到状态后需结合自身布局方向解释这两个数组这使状态本身与具体视觉方向解耦也是它被称为逻辑固定位置的原因。与状态配套的还有一个位置联合类型ColumnPinningPositionexport type ColumnPinningPosition false | start | endfalse表示不固定回到中心区start与end表示两个固定区域。该类型贯穿列实例上的pin、getIsPinned等 API 签名是操作ColumnPinningState时最常出现的参数/返回值类型。默认值与初始化getDefaultColumnPinningState 与 initialState 合并固定功能默认处于无任何列被固定的状态。该默认值由 columnPinningFeature.utils.ts 中的getDefaultColumnPinningState提供export function getDefaultColumnPinningState(): ColumnPinningState { return { start: [], end: [], } }即两个空数组。这一点也有对应的单元测试背书见 columnPinningFeature.utils.test.ts测试断言getDefaultColumnPinningState()返回{ start: [], end: [] }。在表格实例创建时columnPinningFeature.ts 的getInitialState会把用户传入的initialState.columnPinning与默认值做浅合并getInitialState: (initialState) { return { columnPinning: { ...getDefaultColumnPinningState(), ...initialState.columnPinning, }, ...initialState, } }因此如果想在表格初始化时就固定某些列只需在创建表格时传入initialState: { columnPinning: { start: [firstName], end: [], }, }注意initialState.columnPinning只提供初始状态快照当你在后续交互中通过setColumnPinning或column.pin(...)修改状态后再调用resetColumnPinning()时会恢复到这里的初始值。resetColumnPinning(defaultState?)的两个分支行为也由源码明确定义columnPinningFeature.utils.ts不传参或传false克隆table.initialState.columnPinning ?? getDefaultColumnPinningState()即恢复到初始化配置传true忽略 initial state直接重置为空的{ start: [], end: [] }。两种状态管理模式受控与非受控ColumnPinningState可以完全由表格内部管理非受控也可以由外部框架状态层托管受控。两者的接线方式在 columnPinningFeature.types.ts 的TableOptions_ColumnPinning中定义配置项类型作用与默认值enableColumnPinningboolean是否允许列固定默认true设为false可全局禁用列级enablePinning也无法覆盖onColumnPinningChangeOnChangeFnColumnPinningState状态变化回调配合state.columnPinning实现受控模式外部原子atom托管状态时可不传state.columnPinningColumnPinningState受控模式下由外部传入的当前状态快照非受控模式是默认行为用户不传state.columnPinning只依赖initialState给出起点后续固定/取消固定直接改内部状态开箱即用。受控模式下你需要自己持有ColumnPinningState并同时传入state与onColumnPinningChangeconst [columnPinning, setColumnPinning] React.useStateColumnPinningState({ start: [firstName], end: [lastName], }) const table useAppTable({ columns, data, state: { columnPinning }, onColumnPinningChange: setColumnPinning, // ... })官方示例 examples/react/column-pinning/src/main.tsx 的注释同时给出了三种接线方式值得照抄使用// initialState: { columnPinning: { start: [firstName], end: [] } }, // start/end follow layout direction // atoms: { columnPinning: columnPinningAtom }, // preferred: own pinning state with an external atom // state: { columnPinning }, // classic controlled state; pair with onColumnPinningChange // onColumnPinningChange: setColumnPinning, // enableColumnPinning: false, // disable pinning for every column; default true其中atoms是本仓库较新的状态托管方式把columnPinning状态切片放进外部原子如 Jotai由原子自己持有与更新无需再写回调。在 React 的 main.tsx 中通过createTableHook({ features: { columnPinningFeature, ... } })创建带固定能力的 hook 后即可把 pinning 状态以三种方式任意注入。状态如何被写入pin 操作的底层实现固定/取消固定最终都会转化为对ColumnPinningState的更新。所有写操作统一经过table_setColumnPinning(table, updater)columnPinningFeature.utils.ts它把 updater 转发给setStateSlice(table, columnPinning, updater)最终触发onColumnPinningChange并驱动重渲染。而列实例上的column.pin(position)是用户最常用的入口其实现是 column_pin。核心算法分三步收集叶子列对列尤其是分组列调用column.getLeafColumns()取其所有叶子列的 ID所以固定一个分组列等于固定它下辖的全部叶子列双侧去重在返回的新状态中先把这些 ID 同时从old.start与old.end里过滤掉保证同一列不会同时出现在两个数组中追加到目标区start时追加到start数组末尾end时追加到end数组末尾传false则只做去重、不再追加实现取消固定。一个典型的固定到 start 的更新结果// 旧状态: { start: [], end: [] } column.pin(start) // 对 id firstName 的列 // 新状态: { start: [firstName], end: [] }数组顺序即固定区列顺序后固定的列追加在末尾。这一行为在单元测试 columnPinningFeature.utils.test.ts 中有完整覆盖——分别验证了固定到start、固定到end、以及传false取消固定三种场景下的新旧状态转换。状态如何被读取从状态到三区视图ColumnPinningState被写入后会被一系列只读 API 消费形成start / center / end三个渲染分区。按消费层次可分为四组全部定义在 columnPinningFeature.utils.ts 中并通过 columnPinningFeature.ts 挂载到列、行、表实例上1. 列级查询Column APIAPI返回值语义column.getCanPin()boolean该列或其任一叶子列是否可固定由columnDef.enablePinning与table.options.enableColumnPinning双重闸门控制二者默认都为truecolumn.getIsPinned()start \| end \| false读取该列当前所在区域分组列任一叶子在start即报start否则检查end都没有则falsecolumn.getPinnedIndex()number该列在所属固定区数组中的下标用于计算固定区内的排列位次未固定返回0column.pin(position)void写入状态固定到 start/end 或取消固定其中getIsPinned的实现columnPinningFeature.utils.ts直接读取columnPinning原子并按start→end的顺序遍历叶子列 ID因此同一列若同时出现在两个数组中getIsPinned会优先报告start——测试 columnPinningFeature.utils.test.ts 专门断言了这一边界行为。2. 行级查询Row APIAPI语义row.getStartVisibleCells()按state.columnPinning.start顺序返回起始区可见单元格并给每个 cell 打上cell.position start标记row.getEndVisibleCells()按state.columnPinning.end顺序返回结束区可见单元格标记cell.position endrow.getCenterVisibleCells()从所有可见单元格中剔除 start/end 两个数组内的列返回中心区单元格当没有任何固定列时直接返回共享的全量可见单元格数组三个方法分别对应渲染一行的三块区域标记position是为了让渲染层能给固定区单元格附加特定样式。测试断言见 columnPinningFeature.utils.test.ts。3. 表级分区查询Table API针对每个区域表格实例都暴露了一组对称的列/表头/页脚访问器源码见 columnPinningFeature.types.ts列getStartLeafColumns/getEndLeafColumns/getCenterLeafColumns以及各自带可见性过滤的get*VisibleLeafColumns版本还有按位置参数分发的getPinnedLeafColumns(position)与getPinnedVisibleLeafColumns(position)表头组getStartHeaderGroups/getEndHeaderGroups/getCenterHeaderGroups页脚组getStartFooterGroups/getEndFooterGroups/getCenterFooterGroups实现为对应表头组反转复用见 columnPinningFeature.utils.ts扁平表头与叶子表头get*FlatHeaders、get*LeafHeaders叶子表头 扁平表头中过滤掉带子表头的父级汇总判断getIsSomeColumnsPinned(position?)不传位置时检查两个数组是否任一非空传start/end时只查对应数组。中心区表头组还有一个值得注意的细节table_getCenterHeaderGroupscolumnPinningFeature.utils.ts在构建前会先把start与end拼接后从可见叶子列中过滤掉即中心区 可见列 − 固定列。而三个固定区表头组的 memo 依赖都包含columnPinning状态与columnOrder状态见 columnPinningFeature.ts这意味着固定区列顺序同时受固定数组顺序和列排序column order两个状态影响——固定数组只决定哪些列 相对先后列排序决定最终排列。测试 columnPinningFeature.utils.test.ts 验证了修改columnOrder后中心区可见列随之更新的联动行为。实际渲染一个完整可复现的固定列示例把上述状态与 API 串起来就是官方 React 示例 column-pinning 的渲染模式。该示例注释明确说明它使用的是非拆分non-splitAPI——所有列仍在同一个table内固定只改变列在表内的排列位置而不是拆成三个独立表格。表头部分main.tsx为每个可固定列渲染三个操作按钮交互逻辑完整体现了状态读写闭环{!header.isPlaceholder header.column.getCanPin() ( div classNamepin-actions {header.column.getIsPinned() ! start ? ( button onClick{() header.column.pin(start)}{}/button ) : null} {header.column.getIsPinned() ? ( button onClick{() header.column.pin(false)}X/button ) : null} {header.column.getIsPinned() ! end ? ( button onClick{() header.column.pin(end)}{}/button ) : null} /div )}这段代码的模式可以归纳为getCanPin()决定按钮是否出现 →getIsPinned()决定当前显示哪几个按钮 →pin(position)写入 ColumnPinningState → 状态变化驱动重渲染 →getHeaderGroups()按新区间重新排列列。列定义中还保留了enablePinning: false的注释示例main.tsx说明如何阻止特定列被固定。更复杂的三区分离布局如固定列在滚动时保持不动则使用拆分 API分别渲染table.getStartLeafColumns()、table.getCenterLeafColumns()、table.getEndLeafColumns()为三个区域中心区使用row.getCenterVisibleCells()作为滚动主体——这也是getStartVisibleLeafColumns、getCenterVisibleLeafColumns等可见性过滤版本存在的意义它们把固定在数组中但被列可见性隐藏的列过滤掉避免渲染出空白列。与其他功能的协作关系从源码的 memo 依赖可以看出ColumnPinningState并非孤立工作它与几个邻近状态切片紧密协作columnVisibility列可见性所有get*VisibleLeafColumns都会叠加可见性过滤隐藏一个被固定的列不会把它从固定数组中移除只是不再渲染columnOrder列排序中心区与固定区的最终列序都依赖列排序状态固定数组只负责划定哪些列在固定区及其内部相对次序grouping分组分组列的叶子展开会影响固定列的解析因此固定区叶子列的 memo 依赖中也包含grouping状态与groupedColumnMode选项见 columnPinningFeature.ts。小结状态模型一图流ColumnPinningState的整个生命周期可以浓缩为一条闭环initialState.columnPinning (初始快照) │ ▼ getDefaultColumnPinningState() ──合并──► 表格内部 state.columnPinning │ │ │ column.pin(start|end|false) │ setColumnPinning(updater) ▼ ▼ { start: string[], end: string[] } ──────► getIsPinned / getStartVisibleCells / 中心区 剩余未列出的列 getCenterHeaderGroups / getPinnedLeafColumns │ │ └────────── 重渲染 / resetColumnPinning ◄─┘无论你使用 React、Vue、Solid、Svelte 还是原生实现只要遵守start/end两个数组的契约——写入时保持列 ID 唯一、顺序即显示顺序、逻辑位置按布局方向解释——就能获得一致的列固定行为。这份状态定义与配套 API 的完整签名均可继续查阅 ColumnPinningState 接口文档 与 column-pinning 功能类型源码官方 React 示例 column-pinning 则提供了开箱即用的交互式参考实现。【免费下载链接】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),仅供参考