内容简介:图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测试的帧率结果:
图3 - FlatList 滚动帧率图4 - RecyclerListView#1 滚动帧率
图5 - RecyclerListView#2 滚动帧率
图6 - 帧率对比
通过帧率对比可以看出,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 无限列表》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Types and Programming Languages
Benjamin C. Pierce / The MIT Press / 2002-2-1 / USD 95.00
A type system is a syntactic method for automatically checking the absence of certain erroneous behaviors by classifying program phrases according to the kinds of values they compute. The study of typ......一起来看看 《Types and Programming Languages》 这本书的介绍吧!