需求场景
1.长列表,大约超过1000行数据
2.每一行至少8列,每一列都不是单纯的文本节点,都是input,select,checkbox之类的复杂组件,可以理解成可编辑表格。
3.列表可以进行拖拽排序
4.每行最后一列有删除按钮,可以进行删除行操作。
5.每一行有序号标识,序号不能去除,且必须显示出来。
6.锁列
问题
1.每次打开这个长列表,过多的数据,造成大量组件节点需要渲染,200行数据渲染大约需要12s
2.点击删除某一行的时候,行数越靠前,删除,需要重新渲染的节点越多,由于序号不能去掉要显示出来,假如删除第一行,后面2-200行都要重新渲染,就又是12s
优化
1.首先因为序号的原因,导致react本身对长列表key的性能优化失效。
2.需要拖拽排序,并且要显示滚动条,需要滚动加载无法使用。
3.考虑到IE兼容性,IntersectionObserver等一些新特性不能用
第一次优化方案
所以第一阶段考虑的性能优化,是虚拟滚动。virtualization虚拟化功能,是进行滚动加载无限长列表,大数据列表的一种常用性能优化技术,最常用的库,react-virtualized
虚拟化的基本思想
虚拟化就是说我们将需要渲染的内容限制在我们的给的固定容器中,给容器一个股东的高度与高度overflow:auto ,然后数据将在这个容器显示,我们只需要渲染视口容器中适合的数量节点,比如高度为500的容器,每一行50,那么我们一开始是不是渲染10行,就可以填充满视口容器。
对于这个方案,我们需要解决几个问题
1.如果只渲染10行,如何保证滚动条的显示与真实滚动条一致。
2.用户开始滚动后,数据如何显示
3.每一行的行高不是唯一确定的
滚动条问题
我们再开始渲染这个列表时,需要告诉虚拟化的这个组件,我们总的高度的,比如我们每一行是50,一共有1000行,那么高度就是50000。这样我们是可以直接到达真实滚动条显示效果的。
滚动问题
一旦用户开始滚动了,我们可以监听onScroll 事件来获取到滚动的垂直距离y坐标,根据y坐标,我们可以知道当前应该渲染到第几行。
但是如果用户快速滚动,为了不让有闪烁的效果,我们应该提前预先渲染好几行,来防止这种情况,比如我们需要展示10行,我们可以多渲染10行,作为储备,当用户开始滚动的时候,根据设置的阀值,再次提前渲染好额外待展示的行。
当然为了避免用户快速的滚动,导致频繁的渲染行,我们可以使用requestAnimationFrame来实现对渲染速率的控制,从而平滑的滚动
行高的问题
大致的想法是,我们应该在componentDidMount和componentDidUpdate等生命周期进行对高度信息的回调,也就是说我们在渲染每一行的时候,对外提供一行行高的回调,可以进行动态的指定行高,并且要实时的计算总的高度,以保证滚动条的正确显示。
但是这样并未完美解决,我们依赖有大量的渲染浪费。还需要一些优化
列表缓存
由于这里,我们一次显示渲染的数据并不多,我们可以利用react自身的优化,对每一行指定唯一的key,来提升节点的利用和优化。并且我们自身也可以对节点进行缓存,在滚动时遇到缓存中节点时,可直接使用。
列表回收
前面我们说了,要提前预渲染几个节点,以及可视区域的节点,然后滚动过程中,不断的创建新的可视区域节点,我们不做控制,那么最后也是所有的节点都被创建了,这样虽然将一次渲染1000,拆分为了多次,不会在首次打开的时候卡,但是如果这个时候,有数据操作导致列表需要重新渲染,依然会导致全列表的重新渲染,所以我们需要做列表的回收,永远只保证可视区域的节点与上下预渲染的节点数量即可,超出这个范围的节点进行回收缓存。
做到这里,我们基本解决我们的问题,首次打开,以及删除后,重新渲染的性能问题。
但是是不是这就是最优方案了呢,并不是,我们的需求当中还有两点要求,
1.拖拽排序
2.锁列