前言
又一次使用antd-table的糟糕体验,让我不得不去考虑自己手写一个虚拟加载的表格。antd-table不支持表头的虚拟化加载,另外也不支持虚拟化加载的同时支持表头伸缩,而这些功能在大数据领域是很常见的展示需求。对于其他的虚拟加载表格,也没见到很好能同时支持虚拟加载和表头伸缩的库,于是便手写一个虚拟表格自用。
架构准备
首先需要明确虚拟表格的需求点,有哪些是组件需要实现的功能,哪些是用户需要实现的功能。
- 支持纵向和横向虚拟加载
- 支持表头虚拟加载
- 支持表头伸缩
- 支持自定义表内容渲染
- 支持表头和列固定
确定出入参
- 行数 - columnCount
- 列宽度数组(用来横向滚动虚拟化加载)- widths
- renderCell函数(渲染单个单元格)- onCell
- 表格宽高 - width,height
- 行高度(不用数组,因为一般行是相等高度)- rowHeight
- 列数 - rowCount
分析逻辑
由于需要同时支持表格的行和列的虚拟加载,我们需要同时动态计算横向和纵向在视窗内应该展示的表格内容,可以通过每个单元格距离滑动区块的左上角的距离是否在视窗边距内实现。大致逻辑如下图。
按照这个图的计算逻辑,我们需要遍历 row*col次,对所有单元格计算是否在[scrollX,scrollY],[scrollX+width,scrollY+Height]的范围内。然后让在范围内的单元格执行onCell函数即可
优化逻辑
按照上述逻辑会存在一定的性能问题,具体体现在没必要对所有单元格进行遍历,而只需要计算出左上角的单元格的索引,和右下角的单元格索引,即计算出startRow,startCol和endRow,endCol。如下图黄色部分,我们计算出最左边出现的第一个元素的row和col,然后再记录下最后一个出现的元素的row和col,这样只需要 (endRow-startRow) * (endCol-startCol)次循环即可。同时我们可以提前缓存每一列的宽度,这样只需最多columnCount次就可以获取到边界的row和col值。
另外我们使用React进行表格渲染,可以额外使用React Key进行diff优化,即保证所有的key都与col和row有相关性,这样会保证相同的元素出现在视窗中不会重复渲染。
编写代码
缓存并获取表格的总宽度
const {widths} = this.props;
this.cacheTotalWidth = 0;
// 缓存到每一列最右侧的总宽度
this.cacheWidths = [];
widths.forEach((item, index)=>{
this.cacheTotalWidth += item;
this.cacheWidths[index] = this.cacheTotalWidth;
});
获取边界的row和col
const {onCell, rowCount, columnCount, width, height, rowHeight, fixHead} = this.props;
let startColumn ; let endColumn = columnCount - 1; let startRow ;let endRow;
const {scrollLeft, scrollTop} = this.state;
// 开始计算滚动到哪个区间
const endWidth = scrollLeft + width ;
for (let i = 0;i < this.cacheWidths.length;i++) {
const nowWidth = this.cacheWidths[i];
if (startColumn === undefined && nowWidth > scrollLeft) {
startColumn = i;
}
if (nowWidth >= endWidth) {
endColumn = i;
break;
}
}
startRow = Math.floor(scrollTop / rowHeight);
endRow = Math.min(Math.ceil((scrollTop + height) / rowHeight) - 1, rowCount);
进行渲染
const cells = [];
for (let i = startColumn;i <= endColumn;i++) {
for (let j = startRow;j <= endRow;j++) {
cells.push(onCell({rowIndex: j, columnIndex: i, key: `${i}_${j}`, style: {
position: 'absolute',
transform: `translate(${this.cacheWidths[i - 1] || 0}px,${j * rowHeight}px)`,
width: this.widths[i],
height: rowHeight
}}));
}
}
return cells;
搭配Resizeable
Resizeable的核心是动态生成column的宽度数组,因此我们在onResize时,重新设置widths即可
onResize = (index, value)=>{
const {widths} = this.state;
widths[index] = value.size.width;
this.setState({
widths: {...widths}
});
}
最终效果
搭配上样式 此时渲染一个1000*1000且支持resize的数组就很流畅了。
Demo地址:https://mizy.github.io/react-virtual-table/dist/index.html
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!