React Native 大型列表渲染效能比較:FlatList、FlashList 與 @legendapp/list 實測差異

Published on: | Last updated:

今天要來聊聊 React Native 的長列表(long list)渲染問題。這真的…老問題了,但每次開新專案還是會頭痛一下。

你知道的,像聊天室、動態牆、商品列表這種要一直滑一直滑的頁面,只要資料一多,App 就開始給你臉色看。不是記憶體爆炸,就是滑起來卡得跟幻燈片一樣。我自己是覺得,這八成都是列表元件沒選對。

先說結論

很趕時間的話,記住這句就好:小列表、沒幾筆資料的,用 React Native 內建的 FlatList 沒問題。但只要列表長一點,或是有點複雜,請至少從 Shopify 開源的 FlashList 開始考慮。如果你的目標是原生 App 那種絲滑體驗,特別是列表項目很複雜、有影片圖片那種,那真的要看看 @legendapp/list,效能完全是另一個檔次的。

列表元件的回收機制概念圖
列表元件的回收機制概念圖

所以,問題到底在哪?為什麼會卡?

這個問題嘛,其實跟 React Native 的運作方式有關。特別是內建的 FlatList,我說直接點,它有點…笨。你畫面上有 10 個項目,它就產生 10 個元件。當你往下滑,新的項目進來,舊的項目滑出畫面,你猜 FlatList 怎麼做?

它把舊的元件銷毁,然後再重新建立一個新的元件來顯示新資料。你想想看,如果使用者滑得飛快,App 就得瘋狂地「建立、銷毀、建立、銷毀」,CPU 跟記憶體當然一下就飆高,不卡才怪。

這點跟我們在台灣常用的 App 體驗很不一樣,你看像是 LINE 或 Messenger,訊息列表滑起來通常都很順。那是因為原生開發(Native Android/iOS)早就解決這個問題了。他們用的是一種叫做「View Recycling」(視圖回收)的技術。簡單講,滑出畫面的元件不會被丟掉,而是被「回收」,拿去裝新的資料再利用。這樣一來,不管列表有多長,實際在記憶體裡的元件數量永遠都只有螢幕上那幾個,頂多再加幾個備用的。效能當然好。

三個主流列表元件,到底差在哪?

好,理論講完,我們來看看這三個主角:FlatList、FlashList、跟 @legendapp/list。它們的差別,基本上就是「聰明」的程度不同。

🧱 FlatList:內建的,但也就這樣了

這是 React Native官方給你的。優點就是…不用另外裝,開箱即用。但就像前面說的,它沒有真正的回收機制。它有個叫 `windowSize` 的屬性可以稍微優化,但治標不治本,資料一多(大概幾百筆吧),尤其是在中低階的 Android 手機上,卡頓感就非常明顯。

程式碼大概長這樣,超級簡單:

<FlatList
  data={longListData}
  keyExtractor={(item) => item.id}
  renderItem={({ item }) => <MyListItemComponent data={item} />}
/>

⚡ FlashList:Shopify 出的聰明版 FlatList

Shopify 團隊也受不了 FlatList 的效能,所以他們自己搞了一個 FlashList。它的目標很明確:做一個可以「直接替換」FlatList,但效能好上幾個檔次的元件。

它最大的改進就是引入了 View Recycling 的概念。所以滑動起來順暢很多,記憶體佔用也低。老實說,對大部分的 App 來說,FlashList 已經非常夠用了。

不過呢,它有個小小的「妥協」。為了達到高效能,你需要提供一個 `estimatedItemSize` 屬性,也就是「預估」每個列表項目的高度。如果你的列表項目高度都差不多,那很完美。但如果高度變化很大,例如圖文混排的動態牆,這個預估值要是給不準,效能還是會打折扣,甚至出現空白或跳動的狀況。

這是官方文件推薦的作法,你看,就多了那個 `estimatedItemSize`:

import { FlashList } from "@shopify/flash-list";

<FlashList
  data={longListData}
  renderItem={({ item }) => <MyListItemComponent data={item} />}
  estimatedItemSize={100} // 就是這行!你要猜個大概的高度
/>

🧙 @legendapp/list:追求極致順滑的選擇

如果 FlashList 是聰明版,那 @legendapp/list 就是天才版了。我自己是覺得,它的設計哲學跟前面兩者完全不同。它不是在「優化」React Native 的列表,而是幾乎從頭打造一個真正模仿原生 `RecyclerView` (Android) 和 `UICollectionView` (iOS) 行為的元件。

這代表什麼?

  1. 真正的 View Recycling:它只會建立螢幕可見範圍 + α 的幾個元件,然後不斷重複使用,記憶體佔用低到不可思議。
  2. 動態高度沒在怕:它不需要你猜高度!這對複雜佈局來說,真的是救星。
  3. 超高效能:因為底層機制對了,所以就算列表有幾萬筆資料,或是有好幾個水平列表嵌在垂直列表裡(想想 Netflix 首頁),它滑起來還是非常順,幾乎跟原生 App 沒兩樣。

當然,天下沒有白吃的午餐。它的 API 跟 FlatList 有點不一樣,需要稍微花點時間學習。但說真的,這點學習成本換來的效能提升,絕對值得。

模擬 FlatList 與 @legendapp/list 的捲動差異
模擬 FlatList 與 @legendapp/list 的捲動差異

來,一張表看懂怎麼選

我知道,講了這麼多,你可能還是有點亂。沒關係,我直接整理成一個比較表,用我的話說,你應該就懂了。

比較項目 🧱 FlatList (RN 內建) ⚡ FlashList (Shopify) 🧙 @legendapp/list
適合情境 設定頁、選項少少的選單… 總之,那種一眼就看得完的列表。 大部分情況!一般的動態牆、商品列表、訊息列表,基本上它都能搞定。 超長列表 (上萬筆)、超複雜佈局 (像 Netflix、YouTube TV)、需要極致原生體驗的 App。
效能表現 嗯… 就… 能用。資料一多,使用者馬上就能感覺到卡。 非常好!跟 FlatList 比是天壤之別。滑起來順很多。 目前的天花板。幾乎跟原生 App 沒差,順到不像 RN 寫的。
記憶體用量 很吃記憶體。因為它會一直產生新元件,尤其 Android 上特別慘。 低很多。因為開始做 View Recycling 了,所以記憶體控制得不錯。 極低。因為回收機制做得最徹底,不管列表多長,記憶體都很穩定。
上手難度 最簡單,官方標配,不用想。 也很簡單。API 跟 FlatList 差不多,基本上只要換個名字,再加個 `estimatedItemSize` 就行。 需要花點時間看文件。API 設計思維不太一樣,但觀念通了就很快。
最大痛點 效能差,真的只適合簡單場景。拜託不要拿它做無限滾動 那個 `estimatedItemSize`!如果 item 高度動態變化很大,會很頭痛。 社群比較小,遇到問題可能比較難找到人問。還有就是,有些人可能會覺得為了個列表引入新套件有點殺雞用牛刀。
一句話總結 新手入門款,做作業夠用。 CP 值最高的選擇,80% 的問題它都能解決。 追求極致效能的專業玩家首選。

所以,哪些場景該用哪個?

光看規格沒感覺,我們來點實際的例子。

🎬 影音平台 App (像 Netflix、CatchPlay+)

這種 App 首頁通常是「垂直滾動的列表」裡面包著好幾個「水平滾動的列表」(Continue Watching、Trending Now…)。每個項目又都有海報圖、片名、進度條,超級複雜。

最佳選擇:@legendapp/list

為什麼:只有它能輕鬆處理這種「多個列表互相嵌套」的複雜場景,同時還保持順暢。FlashList 在這種情況下還是會有點掙扎。

📱 社群動態牆 (像 Instagram、Dcard)

特色是無限滾動、圖文影音混排、每個貼文高度都不一樣,還可能有留言、廣告插入。

最佳選擇:FlashList 或 @legendapp/list

為什麼:如果你的版面相對單純,FlashList 靠 `estimatedItemSize` 搭配一些優化技巧還能應付。但如果像 Dcard 那樣,留言可以展開、圖片數量不固定,高度變化極大,那 @legendapp/list 不用猜高度的優勢就完全體現出來了。

🛍️ 電商 App (像 momo、PChome 24h)

這也是無限滾動的地獄。商品列表一滑就是幾百、幾千個。而且還要處理篩選、排序後的即時刷新。說到這個,我就想到台灣好幾個大的電商 App,在列表滑動跟篩選的體驗上,有時候還是能感覺到一點點延遲,尤其是在網路不好的時候,圖片載入跟排版重算,很可能就是效能瓶頸。

最佳選擇:FlashList 應該夠用,但 @legendapp/list 會更保險

為什麼:商品列表的 item 高度通常比較固定,很適合 FlashList。但如果你們的 PM 很愛玩花樣,例如在列表中插入「限時特賣」區塊、或是不同商品有不同的標籤樣式,導致高度不一,那用 @legendapp/list 可以省去很多麻煩。

一個簡單的列表元件選擇決策圖
一個簡單的列表元件選擇決策圖

所以,我的建議是?

老實說,現在(2024 年)我開新的 React Native 專案,除非我 100% 確定那個列表只會有十幾二十筆固定的資料,不然我幾乎不會再用 FlatList 了。

我的習慣是,預設就用 FlashList。它提供了巨大的效能改進,而且遷移成本幾乎是零。它就像是「更完美的 FlatList」。

但如果專案一開始就確定是個複雜的社群 App、影音 App,或是任何對「順暢度」有極致要求的產品,我就會直接導入 @legendapp/list。與其等到後面效能出問題了再來重構,不如一開始就用最強的武器。那點學習曲線,跟未來可能要加班優化的時間比起來,真的不算什麼。


那你呢?你在開發 App 時遇過最卡的列表是哪種?聊天室、動態牆還是商品列表?留言分享一下你最後怎麼解決的!

Related to this topic:

Comments

撥打專線 LINE免費通話