十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

TanStack Svelte Table 单元格合并(Cell Spanning)完整指南:行合并、列合并与源码解析

TanStack Svelte Table 单元格合并(Cell Spanning)完整指南:行合并、列合并与源码解析 TanStack Svelte Table 单元格合并Cell Spanning完整指南行合并、列合并与源码解析【免费下载链接】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 的Cell Spanning单元格合并功能展开讲解在 Svelte 项目中如何让相邻数据单元格像 Excel 或原生 HTMLrowspan/colspan一样合并渲染。你会掌握三类能力通过列定义按值自动合并行的spanRows、为汇总行声明横向全宽单元格的spanColumns以及如何在 Svelte 渲染循环中正确处理被覆盖单元格。同时本文会结合仓库中 table-core 的实现源码 与 Svelte 官方示例说明合并索引的计算原理、与排序/过滤/分页/行固定的联动以及它如何与单元格选择功能组合。一、快速上手开启 Cell Spanning1.1 注册 FeatureCell Spanning 是一个按需注册的 feature通过tableFeatures()组合进表格实例。以tanstack/svelte-table为例import { createTable, tableFeatures, cellSpanningFeature, } from tanstack/svelte-table const features tableFeatures({ cellSpanningFeature }) const table createTable({ features, columns, get data() { return data }, })cellSpanningFeature由 stockFeatures.ts 汇总导出并经由 svelte-table 的入口 对外暴露。注册后表格实例与单元格实例上会挂载getCellSpanIndex、getRowSpan、getColSpan、getIsCovered等相关 API。仓库为 Svelte 提供了可直接运行的完整示例代码位于 examples/svelte/cell-spanning入口组件为 App.svelte并配套 Playwright 端到端测试从 DOM 几何与逻辑网格两个层面校验合并结果。1.2 一个关键前提Feature 是无状态的与排序状态、分页状态不同合并跨度永远从当前实际渲染的行重新计算没有任何可持久化、可重置的状态。排序、过滤、分页、行固定只会改变哪些行相邻合并结果随之自然变化。这一点在源码注释中写得很明确见 cellSpanningFeature.ts后文会结合具体行为展开。二、按值合并行spanRows2.1 布尔形式相邻同值合并在列定义上设置spanRows: true即可让该列中值相等的相邻行合并为一个纵向单元格const columns [ columnHelper.accessor(region, { spanRows: true, // 相邻且 region 值相等的行合并 }), ]默认比较使用Object.is。空值null / undefined在默认比较下永远不会合并——一大块空白单元格合并在一起看起来像渲染 Bug而且会把语义上无关的行粘在一起。2.2 谓词形式自定义合并边界如果默认的值全等不满足需求可以传入谓词函数自行决定某一行是否加入当前 run连续合并块。谓词接收RowSpanContext其中包含anchorRow、anchorValue、previousRow、row、value、column、table等字段类型定义见 cellSpanningFeature.types.tscolumnHelper.accessor(createdAt, { spanRows: ({ anchorValue, value }) sameMonth(anchorValue as Date, value as Date), })示例 App.svelte 里有一个更动态的谓词——shift列只有在表格按该列排序时才合并直观展示了谓词对当前列的响应式读取columnHelper.accessor(shift, { header: Shift, sortFn: alphanumeric, // 仅当 shift 列处于排序状态时相等值才合并 spanRows: ({ column, value, anchorValue }) column.getIsSorted() ! false value anchorValue, })关键设计run 是锚定的anchored。每一个候选行都拿它与 run 的第一行anchor比较而不是与上一行比较因此由构造保证 run 具有传递性。谓词每次索引重建时对每个候选行调用一次每次调用会分配一个上下文对象源码注释建议保持谓词轻量见 cellSpanningFeature.types.ts。2.3 行合并不会跨过的边界无论布尔形式还是谓词形式纵向 run 都严格遵守以下边界实现见 cellSpanningFeature.utils.ts 的breaks数组分页边界run 不会跨越页码下一页即使值延续也会以全新单元格开头固定行分区边界被固定的行渲染在独立分区run 不会跨越 top/center/bottom 分区行树位置变化父子行渲染层级不同父行永远不会与其子行合并展开的行场景下只有真正的兄弟节点可以合并分组行分组行绝不参与任何列的 run。三、渲染合并后的单元格Svelte3.1 跳过被覆盖的单元格合并后被上方或左侧单元格覆盖的单元格会报告 span 为0渲染器必须跳过它。这与表头header.rowSpan的0约定一致参见 headers 指南 中的行跨列部分。Svelte 渲染循环如下tbody {#each table.getRowModel().rows as row (row.id)} tr {#each row.getVisibleCells() as cell (cell.id)} !-- span 为 0 表示该单元格被上方或左侧的单元格覆盖必须跳过。 千万不要渲染 rowspan0在 HTML 中它的含义是延伸到行组末尾 会把单元格向下合并到整个 tbody -- {#if cell.getRowSpan() 0 cell.getColSpan() 0} td rowSpan{cell.getRowSpan()} colSpan{cell.getColSpan()} FlexRender {cell} / /td {/if} {/each} /tr {/each} /tbodycell.getRowSpan()返回该单元格纵向跨越的行数不合并时为1被覆盖时为0cell.getColSpan()返回横向跨越的列数语义同上cell.getIsCovered()getRowSpan() 0 || getColSpan() 0的便捷封装见 cellSpanningFeature.utils.ts。如果不需要单独读取两个 span 数字{#if !cell.getIsCovered()}更简洁。⚠️务必不要渲染rowspan0或colspan0属性。在 HTML 中rowspan0表示跨越到行组末尾会把单元格错误地合并到整个 tbody。示例的 e2e 测试专门断言了这一点见 smoke.spec.ts。3.2 Svelte 响应式细节createTable接受get data()这样的 getter 形式Svelte 的响应式状态通过 getter 接入表格。示例中还展示了运行时开关合并的典型用法——用一个$state布尔值驱动enableCellSpanninggetterscript langts let spanningEnabled $state(true) const table createTable({ features, columns, get data() { return data }, get enableCellSpanning() { return spanningEnabled }, }) /script开关关闭后所有单元格回到 span 为1的平铺渲染示例的 e2e 测试验证了这一点smoke.spec.ts。四、声明横向合并spanColumns与汇总行4.1 基本用法横向合并由承载合并内容的列声明通过spanColumns指定。该值逐行解析并且以列实际渲染的顺序计数隐藏列不计数被隐藏的列不会占用 span 的份额列重排自动适配列顺序变化时span 依然覆盖我之后应该被盖住的那些列。columnHelper.accessor(label, { spanColumns: ({ row }) (row.original.isSummary ? Infinity : 1), })Infinity表示覆盖到本 pinned 区域剩余的所有列。超过可用空间的数值会被钳制到单元格所属固定区域的末尾因此列 span 永远不会跨越 start 固定区、中间区、end 固定区之间的边界实现见 cellSpanningFeature.utils.ts。4.2 汇总行示例示例仓库把横向合并用于小计行label列在小计行上跨满整行数据行则保持1。其列定义与渲染如下见 App.sveltesummaryColumnHelper.accessor(label, { header: Shift, // 小计行只渲染一个跨满全部可见列的标签单元格 spanColumns: ({ row }) row.original.kind subtotal ? Infinity : 1, }),渲染时同样遵循跳过 span 为 0 的单元格规则配合rowspan/colspan输出。4.3 矩形约束行 span 与列 span 的组合当一个单元格同时跨越行和列时合并块必须是矩形锚点单元格同时报告两个方向的 span矩形内其余所有单元格至少在一个轴上报告0。为此单元格只有在其列 span 与 run 的列 span 匹配时才加入纵向 run——这一约束也保证了全宽汇总行永远不会与上方数据行发生纵向合并见 cellSpanningFeature.utils.ts。五、跨度如何随数据管道变化跨度的计算位于table.getCellSpanIndex()其 memo 依赖包含了最终分页行模型、行固定、列可见性、列顺序、列固定、分组、列定义与enableCellSpanning等见 cellSpanningFeature.ts。因此任何行模型变化都会触发重新计算排序改变相邻关系按合并列排序会让相等值聚拢产生最大 run按无关列排序通常会打散 run过滤移除行当过滤删掉 run 中间的行时剩余邻居变得相邻并重新合并分页裁剪 runrun 不跨页前文已述行固定固定行渲染在独立分区run 不跨分区。源码特意注释了为何 memo 依赖使用最终分页行模型而非getRowsInDisplayOrder()——后者读取的是分页前的行模型不会被分页 atom 失效会导致分页后跨度残留、run 错误跨页见 cellSpanningFeature.ts。对应的单元测试分布在 cellSpanningFeature.test.ts覆盖了排序改变相邻性、过滤移除 run 中间行后邻居合并、分页边界切分 run、固定行分区边界、列固定下 span 钳制、隐藏被覆盖列后 span 收缩等场景。e2e 测试则用合并面板 同一实例的平铺参考面板双表对比确保任何排序/过滤/分页组合下合并结果都精确对应平铺网格smoke.spec.ts。六、关闭 Cell Spanning支持两级关闭列级优先级更高实现见 cellSpanningFeature.utils.tsconst table createTable({ features, columns, get data() { return data }, enableCellSpanning: false, // 文档级总开关默认 true }) columnHelper.accessor(status, { enableCellSpanning: false, // 单列退出 })关闭后所有单元格报告 span 为1span 索引根本不会被构建见 cellSpanningFeature.types.ts。七、与单元格选择Cell Selection的组合cellSelectionFeature与 cell spanning 可以协同工作注册两者后选择矩形自动扩张选择区域会完整包住它所触碰到的每一个合并单元格因此合并块要么整体被选中要么整体不被选中减选同理——排除合并块的任意一部分整个合并块都被取消选中方向键导航把合并块当作一个停靠点getSelectedCellCount()将合并块计为一次getSelectedCellIds()只返回真正渲染的单元格getSelectedCellRangesData()仍返回完整的行主序网格数据因为被覆盖的单元格承载着真实的底层值。扩张发生在派生选择边界时而非存储选择时。存储的角点保持稳定排序、翻页或切换enableCellSpanning只改变哪些单元格合并派生出的选择会跟随当前跨度变化。这一行为有 e2e 测试佐证拖选跨越合并块后关闭行合并开关选择从 13 个折叠回原始 2×3 矩形的 6 个再打开又回到 13 个smoke.spec.ts。源码层面的扩张逻辑见 cellSelectionFeature.utils.ts。八、已知限制行虚拟化需要额外处理如果 run 的锚点行被滚出渲染窗口被覆盖的行将渲染为空。此时应读取table.getCellSpanIndex()定位锚点在窗口顶部渲染一个钳制后的 span。分组列忽略spanRows分组本身已把重复值折叠为组行再次合并等于合并两次且分组行在任何列中都不会加入 run源码见 cellSpanningFeature.utils.ts。页脚组与tfoot渲染不受影响。九、进一步阅读Svelte 官方示例源码examples/svelte/cell-spanning/src/App.svelte含行合并、汇总行、选择样式三块面板核心实现cellSpanningFeature.ts、cellSpanningFeature.utils.ts类型定义与上下文说明cellSpanningFeature.types.ts单元测试cellSpanningFeature.test.tsSvelte 端到端测试smoke.spec.ts相关指南Svelte 版 单元格选择指南、表头合并约定【免费下载链接】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),仅供参考
返回列表