长列表或者无限下拉列表是最常见的应用场景之一。RN 提供的 ListView 组件,在长列表这种数据量大的场景下,性能堪忧。而在最新的 0.43 版本中,提供了 FlatList 组件,或许就是你需要的高性能长列表解决方案。它足以应对大多数的长列表场景。

测试数据

FlatList 到底行不行,光说不行,先动手测试一下吧。

性能瓶颈主要体现在 Android 这边,所以就用魅族 MX5 测试机,测试无限下拉列表,列表为常见的左文右图的形式。

测试数据如下:

对比 ListView FlatList
1000条时内存 350M 180M
2000条时内存 / 230M
js-fps 4~6 fps 8~20 fps

js-pfs 类似于游戏的画面渲染的帧率,60 为最高。它用于判断 js 线程的繁忙程度,数值越大说明 js 线程运行状态越好,数值越小说明 js 线程运行状态越差。在快速滑动测试 ListView 的时候, js-pfs 的值一直在 4~6 范围波动,即使停止滑动,js-pfs 的值也不能很快恢复正常。而 FlatList 在快速滚动后停止,js-pfs 能够很快的恢复到正常。

内存方面,ListView 滑动到 1000 条时,已经涨到 350M。这时机器已经卡的不行了,所以没法滑到 2000 条并给出相关数据。而 FlatList 滑到 2000 条时的内存,也比 ListView 1000 条时的内存少不少。说明,FlatList 对内存的控制是很优秀的。

主观体验方面:FlatList 快速滑动至 2000 条的过程中全程体验流畅,没有出现卡顿或肉眼可见的掉帧现象。而ListView 滑动到 200 条开始卡顿,页面滑动变得不顺畅,到 500 条渲染极其缓慢,到 1000 条时已经滑不动了。

通过以上的简单的测试,可以看出,FlatList 已经能够应对简单的无限列表的场景。

使用方法

FlatList 有三个核心属性 data renderItem getItemLayout。它继承自 ScrollView 组件,所以拥有 ScrollView 的属性和方法。

data

和 ListView 不同,它没有特殊的 DataSource 数据类型作为传入参数。它接收的仅仅只是一个 Array<object> 作为参数。
参数数组中的每一项,需要包含 key 值作为唯一标示。数据结构如下:

[{title: 'Title Text', key: 'item1'}]

renderItem

和 ListView 的 renderRow 类似,它接收一个函数作为参数,该函数返回一个 ReactElement。函数的第一个参数的 itemdata属性中的每个列表的数据( Array<object> 中的 object) 。这样就将列表元素和数据结合在一起,生成了列表。

 _renderItem = ({item}) => (<TouchableOpacity onPress={() => this._onPress(item)}><Text>{item.title}}</Text><TouchableOpacity/>);...<FlatList data={[{title: 'Title Text', key: 'item1'}]} renderItem={this._renderItem} />

getItemLayout

可选优化项。但是实际测试中,如果不做该项优化,性能会差很多。所以强烈建议做此项优化!
如果不做该项优化,每个列表都需要事先渲染一次,动态地取得其渲染尺寸,然后再真正地渲染到页面中。

如果预先知道列表中的每一项的高度(ITEM_HEIGHT)和其在父组件中的偏移量(offset)和位置(index),就能减少一次渲染。这是很关键的性能优化点。

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

完整代码如下:

// 这里使用 getData 获取假数据
// 数据结构类似于 [{title: 'Title Text', key: 'item1'}]
import getData from './getData';
import TopicRow from './TopicRow';
// 引入 FlatList
import FlatList from 'react-native/Libraries/CustomComponents/Lists/FlatList';export default class Wuba extends Component {constructor(props) {super(props);this.state = {listData: getData(),};}renderItem({item,index}) {return <TopicRow  {...item} id={item.key} />;}render() {return (<FlatListdata = {this.state.listData}renderItem={this.renderItem}onEndReached={()=>{// 到达底部,加载更多列表项this.setState({listData: this.state.listData.concat(getData())});}}getItemLayout={(data, index) => (// 120 是被渲染 item 的高度 ITEM_HEIGHT。{length: 120, offset: 120 * index, index})}/>)}
}

源码分析

FlatList 之所以节约内存、渲染快,是因为它只将用户看到的(和即将看到的)部分真正渲染出来了。而用户看不到的地方,渲染的只是空白元素。渲染空白元素相比渲染真正的列表元素需要内存和计算量会大大减少,这就是性能好的原因。

FlatList 将页面分为 4 部分。初始化部分/上方空白部分/展现部分/下方空白部分。初始化部分,在每次都会渲染;当用户滚动时,根据需求动态的调整(上下)空白部分的高度,并将视窗中的列表元素正确渲染出来。

_usedIndexForKey = false;
const lastInitialIndex = this.props.initialNumToRender - 1;
const {first, last} = this.state;
// 初始化时的 items (10个) ,被正确渲染出来
this._pushCells(cells, 0, lastInitialIndex);
//  first 就是 在视图中(包括要即将在视图)的第一个 item
if (!disableVirtualization && first > lastInitialIndex) {const initBlock = this._getFrameMetricsApprox(lastInitialIndex);const firstSpace = this._getFrameMetricsApprox(first).offset -(initBlock.offset + initBlock.length);// 从第 11 个 items (除去初始化的 10个 items) 到 first 渲染空白元素cells.push(<View key="$lead_spacer" style={{[!horizontal ? 'height' : 'width']: firstSpace}} />);
}
// last 是最后一个在视图(包括要即将在视图)中的元素。
// 从 first 到 last ,即用户看到的界面渲染真正的 item
this._pushCells(cells, Math.max(lastInitialIndex + 1, first), last);
if (!this._hasWarned.keys && _usedIndexForKey) {console.warn('VirtualizedList: missing keys for items, make sure to specify a key property on each ' +'item or provide a custom keyExtractor.');this._hasWarned.keys = true;
}
if (!disableVirtualization && last < itemCount - 1) {const lastFrame = this._getFrameMetricsApprox(last);const end = this.props.getItemLayout ?itemCount - 1 :Math.min(itemCount - 1, this._highestMeasuredFrameIndex);const endFrame = this._getFrameMetricsApprox(end);const tailSpacerLength =(endFrame.offset + endFrame.length) -(lastFrame.offset + lastFrame.length);// last 之后的元素,渲染空白cells.push(<View key="$tail_spacer" style={{[!horizontal ? 'height' : 'width']: tailSpacerLength}} />);
}

既然要使用空白元素去代替实际的列表元素,就需要预先知道实际展现元素的高度(或宽度)和相对位置。如果不知道,就需要先渲染出实际展现元素,在获取完展现元素的高度和相对位置后,再用相同(累计)高度空白元素去代替实际的列表元素。_onCellLayout 就是用于动态计算元素高度的方法,如果事先知道元素的高度和位置,就可以使用上面提到的 getItemLayout 方法,就能跳过 _onCellLayout 这一步,获得更好的性能。

return (// _onCellLayout 就是这里的 _onLayout// 先渲染一次展现元素,通过 onLayout 获取其尺寸等信息<View onLayout={this._onLayout}>{element}</View>
);
..._onCellLayout = (e, cellKey, index) => {// 展现元素尺寸等相关计算const layout = e.nativeEvent.layout;const next = {offset: this._selectOffset(layout),length: this._selectLength(layout),index,inLayout: true,};const curr = this._frames[cellKey];if (!curr ||next.offset !== curr.offset ||next.length !== curr.length ||index !== curr.index) {this._totalCellLength += next.length - (curr ? curr.length : 0);this._totalCellsMeasured += (curr ? 0 : 1);this._averageCellLength = this._totalCellLength / this._totalCellsMeasured;this._frames[cellKey] = next;this._highestMeasuredFrameIndex = Math.max(this._highestMeasuredFrameIndex, index);// 重新渲染一次。最终会调用一次上面分析的源码this._updateCellsToRenderBatcher.schedule();}};

简单分析 FlatList 的源码后,后发现它并没有和 native 端复用逻辑。而且如果有些机器性能极差,渲染过慢,那些假的列表——空白元素就会被用户看到!

那么为什么要基于 RN 的 ScrollView 组件进行性能优化,而不直接使用 Android 或 iOS 提供的列表组件呢?

最简单回答就是:太难了!

由于本人对 RN 底层原理实现只有简单理解。只能引用 Facebook 大神的解释,起一个抛砖引玉的作用。

以 iOS 的 UITableView 为例,所有即将在视窗中呈现的元素都必须同步渲染。这意味着如果渲染过程超过 16ms,就会掉帧。

In UITableView, when an element comes on screen, you have to synchronously render it. This means that you've got less than 16ms to do it. If you don't, then you drop one or multiple frames.

但是问题是,从 RN render 到真正调用 native 代码这个过程本身是异步的,过程中消耗的时间也并不能保证在 16ms 以内。

The problem is in the RN render -> shadow -> yoga -> native loop. You have at least three runloop jumps (dispatch_async(dispatch_get_main_queue(), ...) as well as background thread work, which all work against the required goal.

那么解决方案就是,在一些需要高性能的场景下,让 RN 能够同步的调用 native 代码。这个答案或许就是 ListView 性能问题的终极解决方案。

We are actually starting to experiment more and more with synchronous method calls for other modules, which would allow us to build a native list component that could call renderItem on demand and choose whether to make the call synchronously on the UI thread if it's hi-pri (including the JS, react, and yoga/layout calcs), or on a background thread if it's a low-pri pre-render further off-screen. This native list component might also be able to do proper recycling and other optimizations.

React Native 的 ListView 性能问题已解决相关推荐

  1. React Native之ListView实现九宫格效果

    概述 在安卓原生开发中,ListView是很常用的一个列表控件,那么React Native(RN)如何实现该功能呢?我们来看一下ListView的源码 ListView是基于ScrollView扩展 ...

  2. React Native 之ListView及九宫格布局

    ListView ListView组件用于显示一个垂直的滚动列表,其中的元素之间结构近似而仅数据不同. ListView更适于长列表数据,且元素个数可以增删.和ScrollView不同的是,ListV ...

  3. 【操作系统】Linux系统中直接优化提升CPU性能(已解决)

    文章目录 问题:服务器CPU没有调用最高性能,导致跑算法的时候处理速度慢 一.BIOS方法 二.终端直接设置CPU调节器方法 1.查看当前CPU调节器 2.安装各种依赖库 3.最后安装cpufrequ ...

  4. android listview 动态删除行,[已解决!]关于listview添加了N行之后,删除某一行后,后面的行号如何动态变化?...

    import win.ui; import string; import fsys; import fsys.file; /*DSG{{*/ var winform = ..win.form( rig ...

  5. react native配置环境watchman监控安装失败解决办法

    1,安装出错提示在安装watchman之前先运行 brew link pcre; 2, 还是提升错误不能连接: 3,试试运行,还说不行: 4,给路径授予权限,输入权限密码,这个是重点:sudo cho ...

  6. React Native 调研报告

    Facebook三月份开源了React Native iOS平台的框架,让移动开发人员和web开发者都各自兴奋了一把:native的移动开发者想的比较多的估计是Facebook的那句:"le ...

  7. React Native 学习资源精选仓库

    <React Native Awesome>这里fork过来的,汇集了各类react-native学习资料.工具.组件.开源App.资源下载.以及相关新闻等,只求精不求全.因后面无法 Pu ...

  8. React Native 学习资源精选仓库(汇聚知识,分享精华)

    React Native 学习资源精选仓库(汇聚知识,分享精华) <React Native Awesome>这里fork过来的,汇集了各类react-native学习资料.工具.组件.开 ...

  9. React Native 从入门到原理

    React Native 是最近非常火的一个话题,介绍如何利用 React Native 进行开发的文章和书籍多如牛毛,但面向入门水平并介绍它工作原理的文章却寥寥无几. 本文分为两个部分:上半部分用通 ...

最新文章

  1. Android+git+hudson+gradle持续集成
  2. linux 虚拟机扩展硬盘后扩展到分区
  3. 【MS Word技巧】word如何批量把括号内字体变绿?
  4. 关于lvalue and rvalue
  5. mysql表在线转成分区表_11g普通表在线转换分区表
  6. GridView - Batch Editing - How to cancel editing or disable the editor conditionally
  7. 数字图像的5种增强处理
  8. PAT 1079 Total Sales of Supply Chain[比较]
  9. 13.C++ vector 操作
  10. Ubuntu20.04下使用C++ OpenCV单应性矩阵
  11. 服务器修改文件后撮,xp系统的dns服务器修改办法.doc
  12. 我是如何自学C语言的(一个菜鸟的学习路)
  13. 计算机网络技术——VLAN划分
  14. 2021-05-17
  15. Arch Linux 天坑
  16. 这个简笔画很值得学,哄小孩必备
  17. Modularity and community structure in networks
  18. vs2015不使用方向键移动光标快捷键
  19. python 解析excel表并排重输出到txt
  20. 修改自带input样式input:-internal-autofill-selected为透明色

热门文章

  1. Idea卡在Resolving Maven dependencies
  2. 分布式文件存储FastDFS使用教程(下载安装使用)
  3. 峡谷之巅服务器不稳定,峡谷之巅完全没有高端服务器的水准(经历过的可以进来讨论一下)...
  4. Java学习 --集合和数组
  5. Java中的数据结构之集合
  6. 验证nginx配置文件是否正确
  7. 山东大学软件学院2019数字图像处理期末试题(数媒)
  8. echarts——实现中国地图+世界地图的切换——技能提升
  9. python opencv 简单图像识别,标注 [升级版]
  10. 百度地图API的图层zIndex问题