import React from 'react';
import { Text, View } from 'react-native';
import { FlashList } from '@shopify/flash-list';
const MyList = ({ items }) => {
return (
<FlashList
data={items}
renderItem={({ item }) => (
<View style={{ height: 80, padding: 10 }}>
<Text>{item.title}</Text>
</View>
)}
// Cheia succesului pe Android ieftin: estimare cât mai precisă
estimatedItemSize={80}
keyExtractor={(item) => item.id.toString()}
/>
);
};M-am lovit recent de o problemă clasică pe un proiect de e-commerce cu vreo 12.000 de produse în catalog. Pe simulatoarele noastre de macOS și pe iPhone-urile de test totul mergea brici. Când am pus aplicația pe un Samsung Galaxy A12 de test – un telefon ieftin, de vreo 500 de lei – a început dezastrul. Derularea listei de produse transforma aplicația într-un slideshow de 15 FPS, plin de ecrane albe.
Am decis să compar direct clasicul FlatList din React Native cu FlashList de la Shopify, folosind Perf Monitor-ul integrat pentru a măsura impactul real.
Cum am testat și ce-am măsurat
Setup-ul a fost simplu: o listă infinită cu 10.000 de carduri destul de complexe. Fiecare card avea o imagine randată cu react-native-fast-image, un titlu, o descriere pe două rânduri și un buton de adăugare în coș. Am activat Perf Monitor din Developer Menu și am început să dau scroll rapid, ca un utilizator nervos.
Cu FlatList, rezultatele pe JS Thread au fost dureroase. La un scroll rapid, JS thread-ul scădea frecvent la 10-12 FPS. UI thread-ul rezista pe la 30-40 FPS, dar experiența vizuală era groaznică. Apăreau acele spații albe imense deoarece FlatList șterge din memorie componentele care ies din ecran și nu apucă să le monteze destul de repede pe cele care intră.
Cum funcționează reciclarea sub capotă
La FlatList, când dai scroll în jos, componentele care ies prin partea de sus sunt distruse (unmounted), iar cele noi sunt create de la zero. Asta înseamnă că bridge-ul de React Native este bombardat cu mesaje de creare de noduri în arborele nativ. Pe un procesor slab de MediaTek de pe telefoanele ieftine, procesul ăsta pur și simplu blochează thread-ul de JS.
FlashList, inspirat din lumea nativă de Android (RecyclerView), păstrează un pool de vreo 10-15 celule create. Când o celulă iese din ecran, o ia, îi schimbă doar proprietățile prin props și o pune în josul listei. Pentru Android, e aceeași vedere, doar că textul s-a schimbat. Efortul de randare e aproape zero.
Trecerea la FlashList: Numerele nu mint
Am înlocuit FlatList cu FlashList de la Shopify. Diferența de performanță a fost vizibilă instant, dar hai să ne uităm pe numerele din Perf Monitor:
- FlatList: JS Thread: 12-15 FPS (scroll rapid) | UI Thread: ~35 FPS | Timp de încărcare inițial: ~450ms
- FlashList: JS Thread: 48-52 FPS (scroll rapid) | UI Thread: 58-60 FPS | Timp de încărcare inițial: ~120ms
Am economisit enorm la timpul de randare inițial și am eliminat complet ecranele albe în timpul scroll-ului.
Trade-off-ul sincer: FlashList nu e perfect
Sună prea frumos ca să fie adevărat? Cam este. Reciclarea vine cu un cost de dezvoltare destul de mare.
Dacă celulele tale au înălțimi foarte diferite, trebuie să fii extrem de atent cu parametrul estimatedItemSize. Dacă pui o valoare greșită, o să vezi elemente care sar pe ecran sau layout-uri care se suprapun când dai scroll înapoi.
Un alt bug frecvent de care m-am lovit: starea internă a componentelor. Dacă ai un buton de "Favorite" cu un useState local în interiorul cardului de produs, când elementul se reciclează, starea poate să rămână activă pentru noul produs care apare pe ecran. Trebuie să muți starea în afara componentei sau să folosești chei corecte, altfel te trezești cu bug-uri vizuale greu de debugat.
Dacă ai o listă simplă, sub 100 de elemente, rămâi pe FlatList. Nu merită complexitatea. Dar dacă ai liste lungi, infinite scroll sau e-commerce pe Android entry-level, FlashList e salvator. Voi ce folosiți pentru listele complexe din producție?