原生跨框架的表格组件,三步实现万条不卡
确定在GridManager内实现万条不卡的想法,最早出现于2021年初同郑某人
和杨某人
的饭局上。如今功能已经实现,整理下这个值得记录的过程。
现状分析
在虚拟滚动落地前,针对于现有逻辑的改造是个前置条件。
上方
gif
内演示了在实现万条不卡前,表格组件所拥有的部分能力。由于这些能力搭建于原生跨框架的结构上,所以在需要对渲染逻辑进行较大范围改造时,原先的能力就成为了不小的包袱。【原生跨框架的表格组件,三步实现万条不卡】因此在不影响现有功能的情况下支持万条不卡,需要按序实现以下三个步骤:
- tbody区域的数据驱动改造:
降低功能复杂度
- 差异化更新:
减少DOM节点的更新频率
- 虚拟滚动:
减少DOM节点的更新数量
1、数据驱动
随着js框架的出现,数据驱动改变了前端组件的渲染逻辑。对于一个脱离框架的原生表格组件,虽然需要自行实现数据驱动,但难度其实不大,只需将tbody区域内的所有渲染逻辑进行整合,并向外透出唯一的数据变更接收函数。
这个函数起到一个承上启下的作用,隔离了数据层与视图层。在数据变更时被触发,并根据触发的不同场景对tbody区域进行渲染。
假设这个函数叫
changeTableData
,实现数据驱动后的逻辑如下:// 当数据变更时,函数会被触发,由该函数来判断哪些DOM应该被更新。
changeTableData() {
const data = https://www.it610.com/article/getTableData();
// tbody render
renderTbody(data);
}// 场景一: 分页被执行时
changeTableData();
// 场景二: 某条数据被修改时
changeTableData();
数据驱动只是一个先决条件,差异化更新和虚拟滚动才是解决卡顿的手段。
2、差异化更新
表格组件所依赖的数据无论是什么格式,都会存在一个与渲染结果相匹配的数组。
[{
id: 1,
name: '张三'
},{
id: 2,
name: '李四'
},{
id: 3,
name: '王五'
}]
这个数组在特定情况下,返回结果会存在与已渲染数据相同或部分相同的情况。比如说: 在触发搜索后再次返回了以下数据:
[{
id: 1,
name: '张三丰'
},{
id: 2,
name: '李四'
},{
id: 3,
name: '王五'
},{
id: 4,
name: '赵六'
}]
将两次的返回结果进行比较,可以发现:
- 数组的长度增加1
- 索引为0的name发生了变化
为防止不必要的性能消耗,需要基于数据驱动的逻辑对DOM进行差异化更新。
const diffTableData = https://www.it610.com/article/(oldData, newData) => {
// 通过比对oldData与newData,生成一个新的数组
const diffList = [];
// ...省略掉的比对逻辑
return diffList;
};
通过
diffTableData
函数,比对数据得到以下结果:[{
id: 1,
name: '张三丰'
},
,
,
{
id: 4,
name: '赵六'
}]
在得到排重的结果后,并数据进行渲染。
changeTableData() {
const oldData = https://www.it610.com/article/getOldData();
const newData = getNewData();
// 提供diff函数,将比对后的数据进行渲染,未变更的DOM不更新
const diffData = diffTableData(oldData, newData);
// tbody render
renderTbody(diffData);
}
以上示例仅是用于展示逻辑,完整实现可点击github地址查看
diffTableData
函数。3、虚拟滚动
用户看到的一万条数据,其实在前端只渲染了20条。当原生表格的tbody区域实现数据驱动后,通过在
changeTableData
中提供勾子供scroll
事件调用即可实现虚拟滚动。在勾子内通过滚轴的
scrollTop
计算当前需要显示的数据,通过margin
填充虚拟部分的滚轴高度。并在这些基础上增加一些防抖机制,让虚拟滚动更流畅。changeTableData() {
// 在tbody区域滚轴事件中调用
virtualScrollMap[key] = () => {
// 1、计算当前需要显示的数据,保证tr的最大值不超过20
// 2、通过margin填充虚拟部分的滚轴高度
// 3、增加防抖
};
}// tbody区域的滚轴事件
onScroll = () => {
// 调用changeTableData提供的勾子
virtualScrollMap[key]();
}
以上示例仅是用于展示逻辑,完整实现可点击github地址查点
changeTableData
函数。效果展示 tbody区域实现
数据驱动
、差异化更新
、虚拟滚动
后,在2021年的最后一天,万条不卡最终以即定步骤得以发布,效果如下:new GridManager(table, {
// 启用虚拟滚动 @2.18.0
virtualScroll: {
// 在使用supportTreeData与fullColumn时虚拟滚动无效。
// 使用静态导出,必须配置handler,否则导出数据长度为virtualNum;
// 打印时仅对当前配置virtualNum的条数生效
useVirtualScroll: true,// 实际渲染的Tr数,该数值大于当前页数据长度时,虚拟滚动不生效
virtualNum: 10
},
// ...其它配置项
});
从
gif
中可以看出,虽然存在10000条数据,但tbody区域的tr节点一直保持在10个。table上的margin根据滚动轴的位置不断变化,支撑着表格区域的高度。开源不易 最近听
杨某人
说要搞个组件库,闲聊间想起六年前GridManager的样子,真是一言难尽、一路艰辛。开源不易,希望可以坚持下去。
推荐阅读
- android第三方框架(五)ButterKnife
- 《跨界歌手》:亲情永远比爱情更有泪点
- 标签、语法规范、内联框架、超链接、CSS的编写位置、CSS语法、开发工具、块和内联、常用选择器、后代元素选择器、伪类、伪元素。
- 也许第一步很难,但跨过去就好了
- NeuVector 会是下一个爆款云原生安全神器吗()
- 原生家庭之痛与超越
- GIS跨界融合赋能多领域技术升级,江淮大地新应用成果喜人
- Spring|Spring 框架之 AOP 原理剖析已经出炉!!!预定的童鞋可以识别下发二维码去看了
- 构建App(一)(框架与结构)
- 自我探索之原生家庭1