import React from 'react';
import { FlashList } from '@shopify/flash-list';
import { ProductCard } from './ProductCard';
import type { Product } from './types';
interface Props {
items: Product[];
}
export const ProductFeed = ({ items }: Props) => {
return (
<FlashList
data={items}
renderItem={({ item }) => <ProductCard item={item} />}
estimatedItemSize={180}
keyExtractor={(item) => item.id.toString()}
// Nu băga overrideProps sau key-uri dinamice pe child direct,
// altfel anulezi mecanismul intern de recycling.
/>
);
};Dacă testezi aplicația doar pe simulatorul de Mac sau pe vreun iPhone 14 Pro, totul pare uns cu unt. Realitatea din producție te lovește în cap când primești review-uri de o stea de la oameni cu telefoane de 700 de lei. Am luat recent un Samsung Galaxy A14 (4GB RAM, procesor Exynos chinuit) și am pus pe el un catalog de 1.500 de produse, măsurat la sânge cu Perf Monitor din RN Developer Menu.
Rezultatele mi-au confirmat ce bănuiam: FlatList este, pentru liste lungi și complexe pe Android slab, complet depășit.
Setup-ul de test și aparatul de tortură
Am vrut un test cât mai aproape de viața reală. Fiecare card de produs are o imagine optimizată (FastImage), titlu pe două rânduri, preț, un badge de discount și două butoane mici de acțiune. Fără animații complicate, doar un layout standard de e-commerce.
Am scrollat agresiv în sus și în jos timp de 60 de secunde, cu Perf Monitor pornit peste aplicație, urmărind UI FPS, JS FPS și consumul de memorie.
Cifrele brute: FlatList vs FlashList
Cu FlatList, rezultatele au fost dureroase:
- UI FPS: A căzut rapid de la 60 la 22-28 FPS în timpul unui scroll rapid. Utilizatorul simte asta ca pe un freeze evident.
- JS FPS: A stat undeva între 35 și 45 FPS, cu momente în care a înghețat la 0 când bridge-ul era sufocat de calcule de layout.
- Blank spaces: Ecran alb la greu. Dădeam degetul de două ori rapid și așteptam o secundă întreagă să se deseneze itemii.
- Memorie: A urcat treptat de la 120MB la peste 310MB după câteva minute de scroll continuu.
Cu FlashList (Shopify), pe exact aceleași componente și date:
- UI FPS: S-a menținut stabil între 54 și 59 FPS. Nu a picat niciodată sub 48 FPS, chiar și când dădeam flick-uri agresive.
- JS FPS: Constant la 58-60 FPS.
- Blank spaces: Aproape inexistente. Rareori am prins o jumătate de card albă pentru 50-60 de milisecunde.
- Memorie: S-a plafonat undeva la 165MB și a rămas acolo, datorită mecanismului de reciclare a nodurilor native.
De ce funcționează și unde te mușcă FlashList
FlatList nu reciclează componente native; le distruge pe cele ieșite din view și creează altele noi când dai scroll înapoi. Pe un procesor ieftin, Garbage Collector-ul intră în acțiune non-stop, iar crearea continuă de noduri native omoară UI thread-ul.
FlashList ia ideea de RecyclerView din Android pur. În loc să șteargă nodurile, le ține vii în memorie și doar le pasează noile props-uri. Asta aduce fluiditatea.
Dar există un trade-off serios. La FlashList ești obligat să fii atent la două lucruri:
estimatedItemSize: Dacă pui o valoare din burtă (de exemplu pui 50 și cardul are 250px), scrollbar-ul o ia razna și riști salturi vizibile ciudate pe ecran.- State intern în componentele copil: Dacă
ProductCardare unuseStatelocal (de exemplu, un toggle intern pentru favorite în loc să citească din store/props), reciclarea componentei va face ca noul produs să moștenească starea vechiului produs. Toată starea trebuie să vină din date sau să fie resetată explicit.
Voi mai folosiți FlatList în producție pe ecrane dense sau ați făcut deja switch-ul complet la FlashList?