ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Handsontable 数据与索引体系详解:source data、visual dataset 与 physical/visual 索引映射

Handsontable 数据与索引体系详解:source data、visual dataset 与 physical/visual 索引映射 Handsontable 数据与索引体系详解source data、visual dataset 与 physical/visual 索引映射【免费下载链接】handsontableJavaScript Data Grid / Data Table with a Spreadsheet Look Feel. Works with React, Angular, and Vue. Supported by the Handsontable team ⚡项目地址: https://gitcode.com/gh_mirrors/ha/handsontableHandsontable 在渲染表格时始终维护着两套数据视图你传入的源数据source data与用户当前看到的视觉数据集visual dataset并且用两套不同的行列索引分别定位它们。本文围绕 understanding-data-and-indexes 指南展开解释getData()与getSourceData()系列方法各自操作哪一套视图、排序/移动/隐藏/修剪如何改变两者关系以及如何借助toPhysicalRow()/toVisualRow()等转换方法与底层的 Index Mapper 机制写出不会出错的数据读写代码。读完本文你将能准确区分物理索引与视觉索引的语义、判断任意 API 方法的输入输出使用哪一套索引、理解隐藏与修剪在数据视图层面的本质差异并掌握在批量修改数据时用batch()控制重渲染开销的正确姿势。Background网格为什么维护两套数据视图当你把一个数据集传给 Handsontable 时网格并不是直接照抄显示而是同时维护两个相关联的视图源数据source data是你传入的数据集本身保持你提供的原始顺序。通过getSourceData()及一系列相关方法取回也是你应该持久化到后端的内容。视觉数据集visual dataset是用户当前实际看到的、经过排序、移动、隐藏或修剪之后的数据。通过getData()及一系列相关方法取回。在大多数情况下两个视图的行顺序完全一致区分它们没有意义一旦某个功能为了显示目的而重排或移除了行/列getData()与getSourceData()的结果就会开始分道扬镳。从源码上看这种分离是结构性的Core实例在初始化时分别创建了行、列两个IndexMapper实例作为索引映射的底层设施见 core.tsthis.columnIndexMapper new IndexMapper()、this.rowIndexMapper new IndexMapper()并且getData()/getSourceData()走的是两条完全不同的取数通道见下文。两套视图的工作方式源数据 vs. 视觉数据一个排序示例以一个 4 行的源数据集为例按第一列排序后源数组本身并不会改变——Handsontable 将新顺序单独存储因此两种方式读取会得到不同结果// source data, as provided [ [Ford, 2018], [Audi, 2020], [BMW, 2019], [Toyota, 2021], ] hot.getSourceData(); // - the original 4 rows, in the original order hot.getData(); // - the 4 rows sorted by year: Ford, BMW, Audi, Toyota需要我当初给的数据时用getSourceData()需要用户现在看到的数据时用getData()。同样的划分也适用于单单元格变体getDataAtCell()/setDataAtCell()操作视觉数据集而getSourceDataAtCell()/setSourceDataAtCell()操作源数据。完整的方法清单可参考 Binding to dataRelated API reference。从 core.ts 的实现可以看到getData()无参调用时走datamap.getAll()带范围参数时走datamap.getRange(..., DataMap.DESTINATION_RENDERER)——这个 RENDERER 目标即表示按渲染出来的视觉顺序取数而 getSourceData() 则直接调用dataSource.getData()/dataSource.getByRange()在数据源层面按物理顺序取数不参与任何数据变换。物理索引 vs. 视觉索引Handsontable 用两种不同的行列索引来定位这两套视图物理索引physical index源数据数组中的位置。排序、移动、隐藏、修剪都不会改变它——只有插入或删除行/列才会让它变化。视觉索引visual index用户视野中的位置。只要排序、移动、隐藏或修剪改变了显示顺序视觉索引就会随之改变。方法/配置使用索引用途setDataAtCell()、getDataAtCell()、getDataAtRow()、getDataAtRowProp()视觉索引读写用户当前看到的内容setSourceDataAtCell()、getSourceDataAtCell()、getSourceDataAtRow()物理索引读写底层源数据cells配置函数物理行与列按源数据位置挂接的单元格级设置cell配置数组视觉行与列按显示位置挂接的单元格级设置在源码中getDataAtCell(row, column)的实现是datamap.get(row, datamap.colToProp(column))其 JSDoc 明确标注参数为Visual row index / Visual column index见 core.ts而getSourceDataAtCell(row, column)调用的是dataSource.getAtCell(row, column)参数为物理行索引core.ts。两者虽只有 Source 一词之差背后却是完全不同的索引语义与取数通道。四种转换方法要在两套索引之间翻译使用toPhysicalRow()、toVisualRow()、toPhysicalColumn()、toVisualColumn()。例如把视觉位置 0 的行移动到视觉位置 2 之后视觉位置物理行011220hot.toPhysicalRow(0); // - 1现在显示在第一位的是原本的第 1 行 hot.toVisualRow(0); // - 2原本第一行现在显示在第三位这组方法的完整演算示例可参考 Row movingSet a pre-defined row order 与 Column movingSet a pre-defined column order。在内部Handsontable 还维护着第三种面向 DOM 渲染的renderable可渲染索引但公共 API 方法不接受 renderable 索引——它纯粹是渲染层内部的优化设施。源码中的映射实现Index Mapper这四个转换方法在Core中只是薄封装真正做翻译的是IndexMapper。例如 core.ts 中this.toVisualRow (row) instance.rowIndexMapper.getVisualFromPhysicalIndex(row); this.toPhysicalRow (row) instance.rowIndexMapper.getPhysicalFromVisualIndex(row);而 indexMapper.ts 中的getPhysicalFromVisualIndex()直接读取notTrimmedIndexesCache缓存数组getVisualFromPhysicalIndex()读取fromPhysicalToVisualIndexesCache缓存。也就是说行与列各自的IndexMapper实例indexMapper.ts内部维护了修剪后索引与物理↔视觉两套缓存任何排序、移动、隐藏、修剪操作都会触发缓存重建例如notTrimmedIndexesCache与renderablePhysicalIndexesCache在映射变更时被重新计算见 indexMapper.ts从而保证转换方法始终返回最新的映射结果。各功能如何影响两套数据不同功能在源数据 → DOM链路上的作用点不同这决定了getData()是否还包含这些行/列行为隐藏的行/列修剪的行在getSourceData()中有有在getData()视觉数据集中有无是否推移其他行/列的视觉索引否是是否渲染到 DOM否否隐藏hiding只把行/列从渲染中移除它仍留在视觉数据集中、保留自己的视觉索引。对应的插件级细节见 Row hiding。修剪trimming把行/列从视觉数据集中彻底移除其后的每一行/列视觉索引都会前移一位。对应细节见 Row trimming。理解这一差异对钩子与选择区域处理尤其重要例如在 core.ts 中rowIndexMapper.isHidden(toPhysicalRow(row))这类判断被用来在迭代视觉行时跳过被隐藏的行而修剪过的行在getData()的结果里直接不可见。关于重渲染批量操作请用batch()Handsontable 对每一次被接受的数据变更——哪怕只改一个单元格——都会重新渲染所有可见单元格。因此当需要一次性应用大量变更时应把它们包进batch()让网格只渲染一次。从 core.ts 的实现可以看到batch()的机制进入回调前先suspendRender()suspendExecution()挂起渲染与执行回调执行完毕后统一resumeExecution()resumeRender()把多次变更合并为一次渲染与一次缓存重算。官方示例core.ts展示了在batch()内同时执行多次alter(insert_col_start, ...)、多次setDataAtCell(...)、过滤器filters.filter()与排序columnSorting.sort(...)的典型场景最终表格只重渲染一次、缓存只重算一次。Trade-offs何时操作哪一套数据操作源数据当你要持久化到后端、导出数据或需要与当前显示方式无关地处理整个数据集时使用源数据 APIgetSourceData()系列。操作视觉数据当你要响应用户当前看到或点击的内容时——例如在选区钩子或数据变更钩子内部——使用视觉数据 APIgetData()系列。这也正是 Events and hooks 中提到的根据用户交互取数的推荐姿势。另一个重要的权衡点是引用绑定Handsontable 以引用by reference而非拷贝的方式绑定你的源数据因此你完全可以直接读写传入的数组。但要避免这么做——直接写数组会绕过 hooks、validators 与索引映射导致网格的视觉数据集与数组漂移失步。正确做法见 Binding to dataUnderstand binding as a reference 一节。相关指南与 APIBinding to data —— 将数据载入网格并在数组/对象等数据结构之间做选择。Saving data —— 将变更持久化回后端。Events and hooks —— 响应数据变更包括source参数。Row hiding 与 Row trimming —— 各功能插件级细节。Row moving 与 Column moving —— 在不触碰源数组的前提下重排视觉数据集。getData()、getSourceData()、toPhysicalRow()、toVisualRow()的完整 API 参考见 core.ts 相关实现以及对应的 API 文档条目。核心结论可以浓缩为一句读写用户看到的东西用视觉索引与getData()系列读写你提供的数据用物理索引与getSourceData()系列需要跨界时用toPhysicalRow()/toVisualRow()/toPhysicalColumn()/toVisualColumn()四个转换方法。理解了这层映射关系你就能在排序、移动、隐藏、修剪任意组合的场景下写出索引正确、结果可预期的数据读写代码。【免费下载链接】handsontableJavaScript Data Grid / Data Table with a Spreadsheet Look Feel. Works with React, Angular, and Vue. Supported by the Handsontable team ⚡项目地址: https://gitcode.com/gh_mirrors/ha/handsontable创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表