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 避免使用匿名函數,參考「四、對象創建調用分離」的內容。




最后推薦一下我的個人公眾號:「鹵蛋實驗室」,平時會分享一些前端技術和數據分析的內容,大家感興趣的話可以關注一波:



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM