import React from 'react';
import { View, Text } from 'react-native';
import { FlashList } from '@shopify/flash-list';
const FeedList = ({ posts }) => {
// estimatedItemSize e critic! Am măsurat o medie de 140px pe item.
const renderItem = ({ item }) => (
<View style={{ height: 140, padding: 12, borderBottomWidth: 1 }}>
<Text style={{ fontWeight: 'bold' }}>{item.title}</Text>
<Text numberOfLines={2}>{item.body}</Text>
</View>
);
return (
<FlashList
data={posts}
renderItem={renderItem}
estimatedItemSize={140}
getItemType={(item) => item.type} // ajută masiv la reciclarea corectă a celulelor
/>
);
};Să fim sinceri: pe un iPhone 15 Pro, chiar și cea mai prost scrisă listă din React Native rulează la 120Hz. Problema apare când clientul îți cere ca aplicația să meargă impecabil pe un Samsung Galaxy A12 sau un Xiaomi de acum trei ani. Săptămâna trecută am avut de optimizat un feed infinit cu imagini și text pentru o aplicație de livrări cu peste 12k utilizatori activi și m-am lovit direct de limitările clasicului FlatList.
După ore întregi de tweaking, am decis să pun cap la cap cele două abordări și să măsor performanța brută folosind Perf Monitor-ul din developer menu.
Testul din viața reală pe un hardware obosit
Am pus mâna pe un telefon de test cu 3GB RAM și procesor MediaTek Helio P35 — un adevărat coșmar pentru orice motor de JavaScript. Am generat un feed mock cu 2.000 de carduri complexe (text, avatar, imagine principală și trei butoane de acțiune).
Cu FlatList-ul standard, JS FPS-ul a scăzut instant la 12-15 cadre pe secundă în timpul unui scroll rapid. Utilizatorul simțea fizic cum agață interfața la fiecare trei swipe-uri, iar ecranul rămânea alb secunde bune așteptând randarea. Optimizările clasice precum windowSize={5}, maxToRenderPerBatch={10} sau initialNumToRender={10} au ajutat puțin, urcând thread-ul de JS pe la un 24 FPS chinuit, dar cu prețul unui consum masiv de RAM care risca să arunce un crash de tip Out Of Memory.
Salvarea numită FlashList
Am decis să fac trecerea la @shopify/flash-list. Promisiunea celor de la Shopify este că reciclează celulele complet, exact cum se întâmplă nativ pe Android (RecyclerView) sau iOS (UICollectionView), în loc să distrugă și să recreeze componentele așa cum face FlatList.
Rezultatul pe Perf Monitor a fost spectaculos. Thread-ul de JS a rămas blocat la 57-60 FPS în timpul scroll-ului rapid. UI thread-ul nu a mai scăzut sub 55 FPS. Pe același dispozitiv entry-level, diferența s-a simțit instant: fără ecrane albe, fără blocaje și fără lag la atingere.
Trade-off-ul sincer: Ce dai la schimb?
Deși sună ca o magie, există un preț destul de piperat pe care îl plătești la implementare.
În primul rând, proprietatea estimatedItemSize este critică. Dacă pui o valoare complet greșită, lista va face un scroll sacadat (jumpy scroll) deoarece motorul nu poate calcula corect dimensiunea totală a scroll-ului.
În al doilea rând, reciclarea componentelor înseamnă că starea internă a componentelor din listă se păstrează dacă nu ești atent. Am pățit ca un buton de favorite bifat pe elementul cu ID-ul 3 să apară bifat și pe elementul cu ID-ul 15, doar pentru că acea celulă a fost refolosită. Trebuie să muți absolut toată starea în store-ul global sau în componentul părinte și să folosești props curate.
Pentru liste scurte sau ecrane simple, FlatList este în continuare arhisuficient. Dar dacă ai liste complexe și vrei ca utilizatorii tăi cu telefoane ieftine să nu îți șteargă aplicația din frustrare, efortul de a migra pe FlashList merită 100%. Voi ce folosiți pentru listele complexe din producție?