前言
有些需求需要展示长列表,无限下拉都会一直显示出更多的数据。但是当一个页面展示的DOM节点过多的时候,会造成小程序页面的卡顿严重的会直接白屏。
原因有以下几点:
- 列表数据很大,不断获取下一屏的数据,setData的数据越来越多的时候耗时高
- 渲染DOM 结构多,每次 setData 都需要创建新的虚拟- 树、和旧树 diff 操作耗时都比较高
- DOM 结构多,占用的内存高,造成页面被系统回收的概率变大,会白屏
针对这个场景,小程序官方已经有一个解决方案recycle-view:但是使用之后,我发现了很多问题,比如下一页的页面渲染不完整,或者拉取下一页的数据会闪屏。
这些问题都已经反馈给相关开发,但是还没有得到回复,所以我也不确定是不是我没有用对,万一等了半个月最后得到的结论是,官方组件不能满足我们的场景,那就GG了。所以我只能暂时先追求另外一种解决方案了。
通过查看官方文档跟组件代码,可以看到他们的实现思路是这样的:
由此我猜想,为什么会出现渲染不完的情况,应该是由于它需要靠着用户提供的item的高度来算哪些item需要渲染,然后来计算应该渲染出来的屏幕高度。那如果这个计算渲染屏幕高度偏少,就会有渲染不完的情况,计算的高度偏多,又会有渲染出空白的情况。当然我没有去调试它的代码,毕竟看别人的代码是痛苦的,只是瞎猜一通而已。
既然我猜他是因为高度的问题,才出现那么多问题,同时依赖用户提供高度我觉得总是不靠谱,万一他给错了,就会使得渲染有问题。可不可以不要知道item的高度也可以知道哪些元素被渲染出来呢?
答案是可以的。
我们可以以一屏为一个单位,而不是以一个item为一个单位,这样我无需开发者给我提供他的高度。我自己去记录每一屏的高度,然后onscroll的时候,根据scollTop来计算当前应该渲染哪一屏,我把它首尾两屏的元素也一起加起来算是我总的需要渲染元素。
这一套方案我已经用在了自己的项目:公众号视频频道上。有兴趣的小伙伴可以体验一下,入口是:微信 ->订阅号助手->常读的订阅号第一个入口点入即可。(不过该功能还在灰度中,灰度到的朋友欢迎反馈~没有被灰度到的朋友再等等)
有人可能会说:如果我是一屏拉取所有的数据而不是分屏,那你这个方法就不可行了。确实是这样的,但是我觉得应该没有什么场景需要一次性拉取所有的数据。原因如下:
1、一屏你拉取所有节点,用户根本看不完,所以意义不大
2、数据太大,造成网络传输慢
3、setState也慢造成首屏慢
我感觉没啥好处,坏处倒是挺多的。所以不太建议一屏直接返回所有数据。
实现思路:
这里我们通过改造一个通过分屏无限下拉长列表来说一下整个的实现思路。https://developers.weixin.qq.com/s/I9KsIKmo7yhz
这是一个长列表,它一屏获取20个数据,由于元素都长的一样,所以我用文案标明当前渲染的第几个元素。特别说明一下:由于我每一个item只渲染一个元素,并且渲染的数据也很简单,所以不会有我说的那些卡顿,白屏的问题。但是后面的实践可以用户复杂的项目中。
我的实现方案分为下面两步:
1、将渲染列表的数组list改成二维数组。
2、只渲染当然可视区域的那一屏以及它前后一屏的元素。其他用空白div占位
要做到第二步,我们还需要分成三小步
- 需要知道每一屏的高度,这样我们才能给这个占位的空白div元素设置高度。
- 渲染下一屏last的数据,除了保留last,以及last-1那一屏的渲染,其他节点应该为空。
- 如果是获取非最后一屏幕的,通过监听onscrollTop 获取到scrollTop的值,算取当前应该渲染哪一屏,在重新组装数据setState