如何提高 React Native FlatList 性能

React Native 的 FlatList 性能在大多数情况下是很棒的。但是有时候如果数据量比较大时它也存在一些缺陷。我们可以在网络上找到很多 issues 和博客文章讲述如何提高它的性能。在这里,我试图整理一份关于此问题相对想尽的文档。

事先声明,没有所谓的可以解决所有问题的「银弹」。在实际应用场景中,我们仍需思考哪种方式和解决方案最适合我们的业务需求。

一些关系和概念

一开始使用 FlatList 相关组件进行开发的时候,有很多概念让我困惑。所以让我们先将他们来梳理一下:

  • VirtualizedList 是 FlatList 的底层实现,直接使用 FlatList 和 SectionList 会更加方便。一般来说,仅当想获得比 FlatList 更高的灵活性(比如说在使用 immutable data 而不是 普通数组)的时候,你才应该考虑使用 VirtualizedList。具体可以查看官方文档的 Virtualizedlist 部分。也可以参考Virtualized List 这个组件的实现,它提供 React 网页版本的列表实现,支持显示大量数据,针对数据显示的列表内组件自动回收,拥有出色的性能。
  • Performance 性能 此文中,我们主要指列表性能,即为用户提供顺滑的滚动浏览内容的体验。
  • Memory consumption 内存消耗 列表数据在内存中占用多大,这关系到 APP 的稳定性,如果占用内存过大,可能导致你的应用崩溃(被操作系统终止)。
  • Responsiveness 响应能力 这里指应用对交互的响应能力的快慢。举个例子,如果用户点击列表中的某一项之后要弹出提示或者跳转页面,如果跳转页面很慢的话,我们称之为响应能力比较弱。
  • Blank areas 空白区域 如果列表来不及渲染具体的列表项(如快速滚动列表的过程中),你将看到列表的背景,也就是一片白色。
  • Window 视窗 这里指列表内容的可视化区域。

Props(组件属性)

调整和优化组件属性是一种可行的优化 FlatList 性能的方式。以下是几种关于这种思路的调整方式。

设定 removeClippedSubviews 属性

你可以设定 removeClippedSubviews 属性为 true(默认为 false),如果列表项组件滚动到列表的可视区域之外会自动卸载(unmount)。

优点: 这种方式对内存占用比较友好,FlatList 将仅渲染一部分列表项,而非渲染所有数据,类似 iOS 中的 TableView。

缺点:如果遇到频繁、高速的滚动,会导致大量的列表项组件初始化和卸载动作,虽然内存占用被减少,但随之而来的是计算量的大量增加,对于一些性能不够高的设备来说会出现明显的卡顿现象,影响用户体验。如果列表项组件内包含一些复杂的初始化操作和数据引用,可能会导致一些问题或内存泄漏。根据官方文档标注,开启此设定后在有些情况下会有 bug(比如内容无法显示),需谨慎使用。(尝试设定 removeClippedSubviews 为 true 后,测试一个 50 项列表的渲染,频繁滚动它,可以看到 CPU 占用有 5-10% 的上浮)

maxToRenderPerBatch

可参考官方的 VisualizedList API 说明,摘要如下:每批增量渲染可渲染的最大数量。能立即渲染出的元素数量越多,填充速率就越快,但是响应性可能会有一些损失,因为每个被渲染的元素都可能参与或干扰对按钮点击事件或其他事件的响应。这个属性默认数值是 10。

优点: 如果我们设定一个比较大的数值,每批渲染的列表项比较多,这样在滚动的时候会更小概率出现空白区域(未及时渲染)显示。

缺点:参考文档我们可以了解到新的一批列表项在滚动到可视区域前就开始异步渲染,每批数量越大,所需要的计算量越大。这可能会阻塞 JS 线程,导致应用程序无法快速响应用户的点击事件。如果实际应用场景中需要渲染的列表是静态的、不需要响应用户交互的,将此属性数值调大是一个不错的选择。

updateCellsBatchingPeriod

用于具有较低渲染优先级的元素(比如那些离屏幕相当远的元素)的渲染批次之间的时间间隔。与 maxToRenderPerBatch 具有相同的目的,都是为了在渲染速率和响应性之间获得一个平衡。

这个属性的单位是毫秒,默认值是 50 毫秒。

优点: 可以与 maxToRenderPerBatch 属性配合来调整渲染的节奏,找到更适合你应用场景的方式。

缺点:性能和体验的平衡是很难平衡的,性能和视觉体验的抉择是有点困难的。

initialNumToRender

设定列表组件初始化渲染时默认渲染的列表项数量,默认为 10。如果每一个列表项的高度比较大,也许数值设定的更小会更加合适。当然,需要填充可视区域,否则用户将看到空白区域。

windowSize

设置可视区外最大能被渲染的元素的数量,以可视区的长度为单位。比如说,如果列表占满了整个屏幕,而 windowSize 属性被设置为 21 的话,那渲染的长度为包括当前可见屏幕区域在内,往上 10 个屏幕的长度和往下 10 个屏幕的长度。将 windowSize 设置为一个较小值,能有减小内存消耗并提高性能,但是当你快速滚动列表时,遇到尚未渲染的内容的几率会增大,而这些尚未渲染的内容会暂时性地被空白区块所替代。

优点: 在性能允许的情况下,可以设定一个大的数值来保证更加流畅的用户体验,并避免空白区域显示,比如应用程序仅需要支持几款比较新的特定设备。如果考虑兼容更多的设备(比如已经发布了很久的机型),建议尽量将此数值调低一些。

缺点:如果将数值调大,将会导致更多的内存占用,这对一部分设备来说会比较吃力。如果数值设置的太小,会导致更高频的计算。

legacyImplementation

如果此属性设定为 true,将基于 ListView进行实现。默认值为 false

优点: 一次性渲染列表所有项,没有 VisualizationList组件实现方式带来额外计算开销,用户滚动列表将变得非常流畅。

缺点:如果列表内容很多,将占用很多内存空间,触发内存警告,甚至会导致应用内存占用过大被系统杀死。

disableVirtualization

这个属性已经被取消了,我们这里就不讲了。


List items

除了优化列表属性,也有一些列表项渲染的优化策略。

让列表项组件逻辑尽可能简单

列表项组件越复杂,渲染越慢。我们需要尽可能的避免在列表项组件中编写复杂的业务逻辑,如果你的列表项组件在应用多处复用,建议尽量为数据量较大的列表创建一个单独的、简化逻辑的版本来单独使用。

让组件尽可能变得轻量

避免在列表项组件中加载过多的图片等资源。建议与设计团队沟通,尽可能的减少列表项的交互,通过跳转或其他方式来实现更多交互。

使用 shouldComponentUpdate 生命周期

为列表项组件增加更新判断逻辑。React 的 PureComponent 默认通过数据的浅比较来实现 shouldComponentupdate。由于这种实现方式需要检查你的每一个属性,代价是比较高的。如果我们需要实现更好的性能,我们需要为列表项组件创建严格的属性规则,仅检查可能出现变动的特定属性。如果你的组件足够简单,甚至可以这样做:

shouldComponentUpdate() {
  return false
}

使用经过优化的图片组件

我个人习惯使用 @DylanVannreact-native-fast-image 组件。列表项组件中的每一个图片都是一个 new Image() 实例。图片实力越早完成 loaded 状态,JavaScript 线程将越早被释放资源。

使用 getItemLayout

getItemLayout 是一个可选的优化,用于避免动态测量内容尺寸的开销,不过前提是你可以提前知道内容的高度。如果你的行高是固定的,getItemLayout 用起来就既高效又简单。

getItemLayout = (data, index) => ({
  length: 70,
  offset: 70 * index,
  index
})

使用 keyExtractor

此函数用于为给定的 item 生成一个不重复的 key。Key 的作用是使 React 能够区分同类元素的不同个体,以便在刷新时能够确定其变化的位置,减少重新渲染的开销。若不指定此函数,则默认抽取 item.key 作为 key 值。若 item.key 也不存在,则使用数组下标。

keyExtractor={item => item.id}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335