React Native FlatList 原理解析与性能优化

本文是【React Native 性能优化指南】的一部分内容,因为内容比较具有代表性,所以单独拿出进行讲解;若想获得完整优化建议,可点击原文查看。

在 React Native 开发中,最容易遇到的对性能有一定要求场景就是长列表了。在日常业务实践中,优化做好后,千条数据渲染还是没啥问题的。

虚拟列表前端一直是个经典的话题,核心思想也很简单:只渲染当前展示和即将展示的 View,距离远的 View 用空白 View 展示,从而减少长列表的内存占用。

在 React Native 官网上,🔗 列表配置优化其实说的很好了,我们基本上只要了解清楚几个配置项,然后灵活配置就好。但是问题就出在「了解清楚」这四个字上,本节我会结合图文,给大家讲述清楚这几个配置。

1⃣️ 各种列表间的关系

React Native 有好几个列表组件,先简单介绍一下:

  • ScrollView:会把视图里的所有 View 渲染,直接对接 Native 的滚动列表
  • VirtualizedList:虚拟列表核心文件,使用 ScrollView,长列表优化配置项主要是控制它
  • FlatList:使用 VirtualizedList,实现了一行多列的功能,大部分功能都是 VirtualizedList 提供的
  • SectionList:使用 VirtualizedList,底层使用 VirtualizedSectionList,把二维数据转为一维数据

还有一些其他依赖文件,有个🔗 博文的图总结的挺好的,我这里借用它的图一下:

我们可以看出 VirtualizedList 才是主演,下面我们结合一些示例代码,分析它的配置项。

2⃣️ 列表配置项

讲之前先写个小 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,各个属性我们再用文字描述一下:

1.initialNumToRender

首批应该渲染的元素数量,刚刚盖住首屏最好。而且从 debug 指示条可以看出,这批元素会一直存在于内存中。

2.Viewport

视口高度,就是用户能看到内容,一般就是设备高度。

3.windowSize

渲染区域高度,一般为 Viewport 的整数倍。这里我设置为 3,从 debug 指示条可以看出,它的高度是 Viewport 的 3 倍,上面扩展 1 个屏幕高度,下面扩展 1 个屏幕高度。在这个区域里的内容都会保存在内存里。

将 windowSize 设置为一个较小值,能有减小内存消耗并提高性能,但是快速滚动列表时,遇到未渲染的内容的几率会增大,会看到占位的白色 View。大家可以把 windowSize 设为 1 测试一下,100% 会看到占位 View。

4.Blank areas

空白 View,VirtualizedList 会把渲染区域外的 Item 替换为一个空白 View,用来减少长列表的内存占用。顶部和底部都可以有。

上图是渲染图,我们可以利用 react-devtools 再看看 React 的 Virtual DOM(为了截屏方便,我把 initialNumToRender 和 windowSize 设为 1),可以看出和上面的示意图是一致的。

5.removeClippedSubviews

这个翻译过来叫「裁剪子视图」的属性,文档描述不是很清晰,大意是设为 true 可以提高渲染速度,但是 iOS 上可能会出现 bug。这个属性 VirtualizedList 没有做任何优化,是直接透传给 ScrollView 的。

在 0.59 版本的一次 🔗 commit 里,FlatList 默认 Android 开启此功能,如果你的版本低于 0.59,可以用以下方式开启:

removeClippedSubviews={Platform.OS === 'android'}

6.maxToRenderPerBatch 和 updateCellsBatchingPeriod

VirtualizedList 的数据不是一下子全部渲染的,而是分批次渲染的。这两个属性就是控制增量渲染的。

这两个属性一般是配合着用的,maxToRenderPerBatch 表示每次增量渲染的最大数量,updateCellsBatchingPeriod 表示每次增量渲染的时间间隔

我们可以调节这两个参数来平衡渲染速度和响应速度。但是,调参作为一门玄学,很难得出一个统一的「最佳实践」,所以我们在业务中也没有动过这两个属性,直接用的系统默认值。

2⃣️ ListLtems 优化

📄 ListLtems 优化 文档https://reactnative.cn/docs/optimizing-flatlist-configuration/#list-items

文档中说了好几点优化,其实在前文我都介绍过了,这里再简单提一下:

1.使用 getItemLayout

如果 FlatList(VirtualizedList)的 ListLtem 高度是固定的,那么使用 getItemLayout 就非常的合算。

在源码中(#L1287#L2046),如果不使用 getItemLayout,那么所有的 Cell 的高度,都要调用 View 的 onLayout 动态计算高度,这个运算是需要消耗时间的;如果我们使用了 getItemLayout,VirtualizedList 就直接知道了 Cell 的高度和偏移量,省去了计算,节省了这部分的开销。

在这里我还想提一下几个注意点,希望大家使用 getItemLayout 要多注意一下:

  • 如果 ListItem 高度不固定,使用 getItemLayout 返回固定高度时,因为最终渲染高度和预测高度不一致,会出现页面跳动的问题【🔗 问题链接
  • 如果使用了 ItemSeparatorComponent,分隔线的尺寸也要考虑到 offset 的计算中【🔗 文档链接
  • 如果 FlatList 使用的时候使用了 ListHeaderComponent,也要把 Header 的尺寸考虑到 offset 的计算中【🔗 官方示例代码链接

2.Use simple components & Use light components

使用简单组件,核心就是减少逻辑判断和嵌套,优化方式可以参考「二、减轻渲染压力」的内容。

3.Use shouldComponentUpdate

参考「一、re-render」的内容。

4.Use cached optimized images

参考「三、图片优化那些事」的内容。

5.Use keyExtractor or key

常规优化点了,可以看 React 的文档 🔗 列表 & Key

6.Avoid anonymous function on renderItem

renderItem 避免使用匿名函数,参考「四、对象创建调用分离」的内容。




最后推荐一下我的个人公众号:「卤蛋实验室」,平时会分享一些前端技术和数据分析的内容,大家感兴趣的话可以关注一波:


posted @ 2020-05-18 21:48  卤蛋实验室  阅读(4958)  评论(0编辑  收藏  举报