80981934 2020-05-18
本文是【React Native 性能优化指南】的一部分内容,因为内容比较具有代表性,所以单独拿出进行讲解;若想获得完整优化建议,可点击原文查看。
在 React Native 开发中,最容易遇到的对性能有一定要求场景就是长列表了。在日常业务实践中,优化做好后,千条数据渲染还是没啥问题的。
虚拟列表前端一直是个经典的话题,核心思想也很简单:只渲染当前展示和即将展示的 View,距离远的 View 用空白 View 展示,从而减少长列表的内存占用。
在 React Native 官网上,?? 列表配置优化其实说的很好了,我们基本上只要了解清楚几个配置项,然后灵活配置就好。但是问题就出在「了解清楚」这四个字上,本节我会结合图文,给大家讲述清楚这几个配置。
React Native 有好几个列表组件,先简单介绍一下:
还有一些其他依赖文件,有个?? 博文的图总结的挺好的,我这里借用它的图一下:
我们可以看出 VirtualizedList 才是主演,下面我们结合一些示例代码,分析它的配置项。
讲之前先写个小 demo。demo 非常简单,一个基于 FlatList 的奇偶行颜色不同的列表。
export default class App extends React.Component { renderItem = item => { return ( <Text style={{ backgroundColor: item.index % 2 === 0 ? ‘green‘ : ‘blue‘, }}> {‘第 ‘ + (item.index + 1) + ‘ 个‘} </Text> ); } render() { let data = []; for (let i = 0; i < 1000; i++) { data.push({key: i}); } return ( <View style={{flex: 1}}> <FlatList data={data} renderItem={this.renderItem} initialNumToRender={3} // 首批渲染的元素数量 windowSize={3} // 渲染区域高度 removeClippedSubviews={Platform.OS === ‘android‘} // 是否裁剪子视图 maxToRenderPerBatch={10} // 增量渲染最大数量 updateCellsBatchingPeriod={50} // 增量渲染时间间隔 debug // 开启 debug 模式 /> </View> ); } }
VirtualizedList 有个 debug 的配置项,开启后会在视图右侧显示虚拟列表的显示情况。
这个属性文档中没有说,是翻?? 源码发现的,我发现开启它后用来演示讲解还是很方便的,可以很直观的学习 initialNumToRender、windowSize、Viewport,Blank areas 等概念。
下面是开启 debug 后的 demo 截屏:
上面的图还是很清晰的,右侧 debug 指示条的黄色部分表示内存中 Item,各个属性我们再用文字描述一下:
首批应该渲染的元素数量,刚刚盖住首屏最好。而且从 debug 指示条可以看出,这批元素会一直存在于内存中。
视口高度,就是用户能看到内容,一般就是设备高度。
渲染区域高度,一般为 Viewport 的整数倍。这里我设置为 3,从 debug 指示条可以看出,它的高度是 Viewport 的 3 倍,上面扩展 1 个屏幕高度,下面扩展 1 个屏幕高度。在这个区域里的内容都会保存在内存里。
将 windowSize 设置为一个较小值,能有减小内存消耗并提高性能,但是快速滚动列表时,遇到未渲染的内容的几率会增大,会看到占位的白色 View。大家可以把 windowSize 设为 1 测试一下,100% 会看到占位 View。
空白 View,VirtualizedList 会把渲染区域外的 Item 替换为一个空白 View,用来减少长列表的内存占用。顶部和底部都可以有。
上图是渲染图,我们可以利用 react-devtools 再看看 React 的 Virtual DOM(为了截屏方便,我把 initialNumToRender 和 windowSize 设为 1),可以看出和上面的示意图是一致的。
这个翻译过来叫「裁剪子视图」的属性,文档描述不是很清晰,大意是设为 true 可以提高渲染速度,但是 iOS 上可能会出现 bug。这个属性 VirtualizedList 没有做任何优化,是直接透传给 ScrollView 的。
在 0.59 版本的一次 ?? commit 里,FlatList 默认 Android 开启此功能,如果你的版本低于 0.59,可以用以下方式开启:
removeClippedSubviews={Platform.OS === ‘android‘}
VirtualizedList 的数据不是一下子全部渲染的,而是分批次渲染的。这两个属性就是控制增量渲染的。
这两个属性一般是配合着用的,maxToRenderPerBatch 表示每次增量渲染的最大数量,updateCellsBatchingPeriod 表示每次增量渲染的时间间隔。
我们可以调节这两个参数来平衡渲染速度和响应速度。但是,调参作为一门玄学,很难得出一个统一的「最佳实践」,所以我们在业务中也没有动过这两个属性,直接用的系统默认值。
?? ListLtems 优化 文档:https://reactnative.cn/docs/optimizing-flatlist-configuration/#list-items
文档中说了好几点优化,其实在前文我都介绍过了,这里再简单提一下:
如果 FlatList(VirtualizedList)的 ListLtem 高度是固定的,那么使用 getItemLayout 就非常的合算。
在源码中(#L1287、#L2046),如果不使用 getItemLayout,那么所有的 Cell 的高度,都要调用 View 的 onLayout 动态计算高度,这个运算是需要消耗时间的;如果我们使用了 getItemLayout,VirtualizedList 就直接知道了 Cell 的高度和偏移量,省去了计算,节省了这部分的开销。
在这里我还想提一下几个注意点,希望大家使用 getItemLayout 要多注意一下:
ItemSeparatorComponent
,分隔线的尺寸也要考虑到 offset 的计算中【?? 文档链接】ListHeaderComponent
,也要把 Header 的尺寸考虑到 offset 的计算中【?? 官方示例代码链接】使用简单组件,核心就是减少逻辑判断和嵌套,优化方式可以参考「二、减轻渲染压力」的内容。
参考「一、re-render」的内容。
参考「三、图片优化那些事」的内容。
常规优化点了,可以看 React 的文档 ?? 列表 & Key。
renderItem 避免使用匿名函数,参考「四、对象创建调用分离」的内容。
最后推荐一下我的个人公众号:「卤蛋实验室」,平时会分享一些前端技术和数据分析的内容,大家感兴趣的话可以关注一波: