实践指南:vxe-table单元格合并规则的高效封装与性能优化

张开发
2026/4/11 9:39:20 15 分钟阅读

分享文章

实践指南:vxe-table单元格合并规则的高效封装与性能优化
1. 为什么需要封装vxe-table的单元格合并功能第一次接触vxe-table的单元格合并时我直接使用了官方提供的merge-cells参数。当时项目中有个报表需求需要根据产品类别合并相同数据的单元格。简单几行代码就实现了功能看起来效果不错。但随着数据量增加到上千条时页面开始出现明显的卡顿滚动时甚至会出现白屏。经过排查发现vxe-table的单元格合并功能在底层实现上会遍历所有数据来计算合并规则。当数据量大时这个计算过程会阻塞主线程导致渲染延迟。更糟糕的是如果同时开启了虚拟滚动virtual-scroll合并后的单元格高度计算会出现错乱造成显示异常。这就是为什么我们需要对单元格合并进行封装和优化性能问题原生合并计算在大数据量下性能堪忧复用困难不同页面的合并逻辑需要重复编写维护成本业务变更时需要修改多处代码虚拟滚动冲突合并与虚拟滚动同时使用时需要特殊处理2. 单元格合并的两种典型场景与封装方案2.1 处理多层级嵌套数据最近在做一个电商后台项目时遇到这样的数据结构const goodsData [ { category: 电子产品, items: [ { name: 手机, price: 3999, stock: 100 }, { name: 笔记本, price: 5999, stock: 50 } ] }, { category: 家居用品, items: [ { name: 台灯, price: 199, stock: 200 }, { name: 椅子, price: 299, stock: 150 } ] } ]我们需要将相同类别的商品合并显示。这时候可以这样封装合并逻辑/** * 处理树形结构数据的单元格合并 * param {Array} treeData 树形数据 * param {String} childrenKey 子节点键名 * param {Array} mergeCols 需要合并的列索引 */ function mergeTreeData(treeData, childrenKey, mergeCols) { let flatData [] let mergeRules [] let rowIndex 0 treeData.forEach(item { const children item[childrenKey] || [] const rowspan children.length || 1 // 生成合并规则 mergeCols.forEach(col { mergeRules.push({ row: rowIndex, col, rowspan, colspan: 1 }) }) // 扁平化数据 children.forEach(child { flatData.push({ ...item, ...child }) }) rowIndex rowspan }) return { data: flatData, mergeCells: mergeRules } }使用时只需要const { data, mergeCells } mergeTreeData(goodsData, items, [0])2.2 处理平级数据的重复项合并另一种常见场景是合并连续相同的值比如下面这个员工考勤表const attendanceData [ { date: 2023-01-01, name: 张三, status: 正常 }, { date: 2023-01-01, name: 李四, status: 迟到 }, { date: 2023-01-02, name: 王五, status: 正常 }, { date: 2023-01-02, name: 赵六, status: 正常 } ]针对这种场景我封装了这样的合并方法function mergeSameValues(data, field, mergeCols) { const mergeRules [] let currentValue null let startRow 0 data.forEach((row, index) { if (currentValue ! row[field]) { // 计算上一个值的合并范围 if (currentValue ! null index startRow) { const rowspan index - startRow mergeCols.forEach(col { mergeRules.push({ row: startRow, col, rowspan, colspan: 1 }) }) } currentValue row[field] startRow index } }) // 处理最后一组数据 if (startRow data.length - 1) { const rowspan data.length - startRow mergeCols.forEach(col { mergeRules.push({ row: startRow, col, rowspan, colspan: 1 }) }) } return mergeRules }3. 性能优化实战经验3.1 虚拟滚动与单元格合并的平衡术在数据量超过500条时我强烈建议开启虚拟滚动。但正如前文所说虚拟滚动和单元格合并是天生的冤家。经过多次尝试我找到了几个可行的解决方案分页优先能分页的场景尽量使用分页这是最简单的优化方案动态合并只合并可视区域内的单元格滚动时动态计算分层渲染先渲染未合并的表格等合并计算完成后再应用合并规则这里分享一个动态合并的实现思路// 在表格滚动事件中动态计算合并规则 const handleScroll debounce(({ scrollTop }) { const startIndex Math.floor(scrollTop / rowHeight) const endIndex startIndex visibleRowCount // 只计算可视区域的合并规则 const visibleData fullData.slice(startIndex, endIndex) const mergeRules calculateMergeRules(visibleData) tableRef.value.setMergeCells(mergeRules) }, 100)3.2 大数据下的计算优化当数据量实在太大比如上万条时我总结了几个优化技巧Web Worker将合并计算放到Worker线程中避免阻塞UI增量计算数据变化时只重新计算受影响的部分缓存策略对相同的合并规则进行缓存这里给出一个Web Worker的示例// worker.js self.onmessage function(e) { const { data, type } e.data let result switch(type) { case mergeTree: result mergeTreeData(data.tree, data.key, data.cols) break case mergeSame: result mergeSameValues(data.list, data.field, data.cols) break } self.postMessage(result) } // 主线程使用 const worker new Worker(worker.js) worker.postMessage({ type: mergeTree, data: { tree: bigData, key: items, cols: [0,1,2] } }) worker.onmessage (e) { tableData.value e.data.data mergeCells.value e.data.mergeCells }4. 封装成可复用的组件经过多个项目的实践我最终将这套方案封装成了一个独立的组件template vxe-table reftableRef :dataprocessedData :merge-cellsmergeRules scrollhandleScroll / /template script import { mergeTreeData, mergeSameValues } from ./mergeUtils import { useVirtualMerge } from ./virtualMerge export default { props: { data: Array, mergeType: String, // tree | same mergeOptions: Object }, setup(props) { const { processedData, mergeRules, handleScroll } useVirtualMerge( props.data, props.mergeType, props.mergeOptions ) return { processedData, mergeRules, handleScroll } } } /script这个组件的主要特点包括支持两种合并模式树形结构和平级合并内置虚拟滚动优化提供Web Worker选项可配置的缓存策略使用时只需要MergeTable :datagoodsData merge-typetree :merge-options{ key: items, cols: [0,1], useWorker: true } /在最近的一个项目中这个组件成功处理了超过1万条数据的合并展示滚动流畅没有任何卡顿。关键是要根据实际场景选择合适的合并策略和优化方案。

更多文章