今天要來聊聊 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) 行為的元件。
這代表什麼?
- 真正的 View Recycling:它只會建立螢幕可見範圍 + α 的幾個元件,然後不斷重複使用,記憶體佔用低到不可思議。
- 動態高度沒在怕:它不需要你猜高度!這對複雜佈局來說,真的是救星。
- 超高效能:因為底層機制對了,所以就算列表有幾萬筆資料,或是有好幾個水平列表嵌在垂直列表裡(想想 Netflix 首頁),它滑起來還是非常順,幾乎跟原生 App 沒兩樣。
當然,天下沒有白吃的午餐。它的 API 跟 FlatList 有點不一樣,需要稍微花點時間學習。但說真的,這點學習成本換來的效能提升,絕對值得。
來,一張表看懂怎麼選
我知道,講了這麼多,你可能還是有點亂。沒關係,我直接整理成一個比較表,用我的話說,你應該就懂了。
| 比較項目 | 🧱 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 時遇過最卡的列表是哪種?聊天室、動態牆還是商品列表?留言分享一下你最後怎麼解決的!
