性能更好的 React Native 无限列表

栏目: IOS · Android · 发布时间: 5年前

内容简介:图1 - 门面页面(我们称为部落一级页,此处为广告 :-) )该页面是由多个Tab组成,每个tab基本上都是无限下拉的列表。在 React Native 中,可以用作列表的组件,常见的有:

在【58部落】的业务场景下,存在较多的列表页面。整个产品的“门面”——入口页面,常驻在58APP下方的“发现”tab,所以要求有较高的用户体验。作为一个初中期的社区产品,很多功能还不够完善和稳定,因此要求能较快的功能迭代。兼具体验和快速迭代的要求,在58APP中,我们的选择是以 React Native 来进行页面的开发。

图1 - 门面页面(我们称为部落一级页,此处为广告 :-) )

该页面是由多个Tab组成,每个tab基本上都是无限下拉的列表。在 React Native 中,可以用作列表的组件,常见的有:

  • ListView

  • SectionList

当然还有官方支持的高性能的简单列表组件:

  • FlatList

但即使是 React Native 官方支持的性能最好 FlatList 组件,在Android的一些机型上的表现也差强人意,特别是使用超过两年的Android手机,基本上就是到非常卡的状态了。

所以,今天介绍下在Android上表现更好的、性能更优的 React Native 列表组件:

  • RecyclerListView

RecyclerListView 是什么

RecyclerListView 是一个高性能的列表(listview)组件,同时支持 React Native 和 Web ,并且可用于复杂的列表。RecyclerListView 组件的实现灵感,来自于 Android  RecyclerView 原生组件及iOS  UICollectionView 原生组件。

为什么需要RecyclerListView

我们知道,React Native 的其他列表组件如 ListView ,会一次性创建所有的列表单元格—— cell 。如果列表数据比较多,则会创建很多的视图对象,而视图对象是非常消耗内存的。所以, ListView 组件,对于我们业务中的这种无限列表,基本上是不可以用的。

对于React Native 官方提供的高性能的列表组件 FlatList , 前文提到,在Android设备上的表现,并不是十分友好。它的实现原理,是将列表中不在可视区域内的视图,进行回收,然后根据页面的滚动,不断的渲染出现在可视区域内的视图。这里需要注意的是, FlatList 是将不可见的视图回收,从内存中清除了,下次需要的时候,再重新创建。 这就要求设备在滚动的时候,能快速的创建出需要的视图,才能让列表流畅的展现在用户面前 。而问题也就出现在这里,Android设备因为老化等原因,计算力等跟不上,加之React Native 本身 JS 层与 Native 层之间交互的一些问题(这里不做深入追究),导致创建视图的速度达不到使列表流畅滚动的要求。

那怎样来解决这样的问题呢?

RecyclerListView 受到 Android  RecyclerView  和 iOS  UICollectionView  的启发,进行两方面的优化:

  • 仅创建可见区域的视图,这步与 FlatList 是一致的。

  • cell recycling ,重用单元格,这个做法是 FlatList 缺乏的。

对于程序来说,视图对象的创建是非常昂贵的,并且伴随着内存的消耗。意味着如果不断的创建视图,在列表滚动的过程中,内存占用量会不断增加。 FlatList 中将不可见的视图从内存中移除,这是一个比较好的优化手段,但同时也会导致大量的视图重新创建以及垃圾回收。

RecyclerListView 通过对不可见视图对象进行缓存及重复利用,一方面不会创建大量的视图对象,另一方面也不需要频繁的垃圾回收。

基于这样的理论,所以RecyclerListView的性能是会优于FlatList的,实际结果会从下面的实践中得知。

RecyclerListView怎么使用

RecyclerListView 的使用比较简单,相对于 FlatList 通过 getItemLayout 来优化布局需要提供 offset ——相对于FlatList组件对顶部的一个偏移值来说,RecyclerListView 只需要知道对应 cell 的高度值即可。对于复杂列表来说,RecyclerListView 的这种方式,大大优于FlatList使用方式。

一个 RecyclerListView 组件必要的 props 有 :

  • dataProvider

  • layoutProvider

  • rowRenderer

一个最简单的示例(点击底部阅读原文)

为了进行 cell-recycling ,RecyclerListView要求对每个 cell (通常也叫Item)定义一个 type ,根据 type 设置 cell dim.width dim.height

this._layoutProvider = new LayoutProvider(
            index => {
                if (index % 3 === 0) {
                    return ViewTypes.FULL;
                } 
                ...
            },
            (type, dim) => {
                switch (type) {
                    case ViewTypes.HALF_LEFT:
                        dim.width = width / 2;
                        dim.height = 160;
                        break;
                    ...
                }
            }
        );

rowRenderer 负责渲染一个 cell ,同样是根据 type 来进行渲染:

_rowRenderer(type, data) {
    switch (type) {
        case ViewTypes.HALF_LEFT:
            return (
                <CellContainer style={styles.containerGridLeft}>
                    <Text>Data: {data}</Text>
                </CellContainer>
            );
        ...
      }
}

当然在我们的实际业务场景中不可能这么简单,页面滚动需要进行一些处理啊,滚动到最底部需要加载下一页等等都是最常见的业务场景,RecyclerListView这些也都支持得比较好,以下是一些常见的 props:

  • onScroll: 列表滚动时触发;

  • onEndReached: 列表触底时触发;

  • onEndReachedThreshold: 列表距离底部多大距离时触发, 这里是具体到底部的像素值,与FlatList几屏的数值是有区别的 ;

  • onVisibleIndexesChanged: 可见元素,滚动时实时触发;

  • renderFooter: 渲染列表footer。

实际业务怎么处理

在我们的业务场景中,在列表中包含5类 cell :

  • 普通帖子

  • 置顶banner

  • 推荐部落

  • 推荐话题

  • 通知公告

后期应该还会增加其他的类型。后4类基本从 dim.height 上来讲,是不会根据内容变化的,所以还比较简单,定义固定的 type 即可。

对于“普通帖子”这个类型来讲,就相对来说比较复杂了,示例其中一种情况如下图:

图2 - 普通帖子的常见样式

其中有两部分是固定有的:

  • header:发帖者信息等

  • footer: 帖子回复,点赞等数据

其他部分就是根据帖子内容,有,无或者几种形态变化了,如帖子内容可展示为一行或者两行,帖子中的图片分为一图、二图、三图模式等等。

所以这里就出现了一个上述demo中没法解决的问题,“普通帖子”这种类型,我们单单定义一个 type ,不进行其他处理,会存在一些问题。解决这个问题,在我们的业务中,测试了两种方式:

  • 1.仅定义为一个 type ,记为 RecyclerListView#1 。 通过其内容,计算出每个 cell 的高度,并存储到原始数据中,在 layoutProvider 中获取。

    this._layoutProvider = new LayoutProvider(
        index => {
            ...
        },
        (type, dim, index) => {
        // 注意这里的第三个参数
        // 比如原始数据存在 this.data 中
        if(type==='card'){
            dim.height = this.data[index].height ;
        } 
        ...
    })
  • 2.将“普通帖子”,拆分成多个组成部分,记为 RecyclerListView#2

    // 如一条帖子的数据是这样的
    const data = {
        title:'标题',
        context:'内容',
        pics:['https://pic1.58cdn.com.cn/1.png'] ,// 图片
        user:{} ,// 用户信息
        replynum:300 // 回复信息
        hotAnswers:[]
        ...
    }

    根据展示规则,把用户信息等拆成一条,作为 header 这种 type ,把 title 拆成一条,作为 title 这种 type ,一个图片拆成一种 type ,两个图片的又拆成另一种 type ......,这样,每个 type 就基本上比较单纯, type 的高度值也基本能固定了。

从理论上来讲,第二种方式心梗应该是会优于第一种方式(具体回顾RecyclerListView的实现方式及原理)。

性能对比

以下是用OPPO R9测试的帧率结果:

性能更好的 React Native 无限列表

图3 - FlatList 滚动帧率图4 - RecyclerListView#1 滚动帧率

性能更好的 React Native 无限列表

图5 - RecyclerListView#2 滚动帧率

性能更好的 React Native 无限列表

图6 - 帧率对比

性能更好的 React Native 无限列表

通过帧率对比可以看出,RecyclerListView的滚动帧率是远大于FlatList的。FlatList在滚动时帧率波动比较严重,上手体验会发现比较卡顿且较多白屏现象。相对来说,RecyclerListView 的帧率变化相对稳定,基本都能维持到 35fps 以上,平均值在46fps 左右。

RecyclerListView#1 和 RecyclerListView#2, 整体帧率差距不是很明显,在该机型上得不出很正确的结论,就目前的情况来看,这种结果倒是我们作为开发者希望看到的结果。因为相对应的,对数据进行拆分不仅为增加数据量,并且从开发体验上来说,也会增加较大成本,开发体验并不好。

RecyclerListView#1 和 RecyclerListView#2 的比对,还需要更多的设备去验证。

5. 开发建议和场景限制

  • 列表能简单,尽量简单

  • 数据项能不拆,尽量不拆;拆是个大坑

  • 因为 cell recycling , 所以  cell 内部不能保留状态,如果需要数据变化,一定要在外部进行存储,如用redux等

  • 列表项( cell )删除会存在一定问题,特别是对于数据需要进行拆分的列表

其他开发建议参见 RecyclerListView Performance: https://github.com/Flipkart/recyclerlistview/tree/master/docs/guides/performance

性能更好的 React Native 无限列表


以上所述就是小编给大家介绍的《性能更好的 React Native 无限列表》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

计算几何

计算几何

奥罗克 / 机械工业 / 2005-4 / 49.00元

本书介绍了在计算机图形学、机器人和工业设计领域逐渐兴起的几何算法的设计和实现。计算几何中使用的基本技术包括多边形三角剖分、凸包、Voronoi图、排列、几何查找、运动计划等。虽然自主处理只涉及数学基础知识领域的一部分,但是它却和当今该研究领域的前沿课题相关。因此,专业的程序员会发现本书是一本不可多得的参考书。   与上一版相比,本版包括以下几方面的新内容:多边形三角剖分的随机化算法、平面点定......一起来看看 《计算几何》 这本书的介绍吧!

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具