import React, { memo } from 'react';
import { Text, Pressable, StyleSheet } from 'react-native';
import { FlashList } from '@shopify/flash-list';
type Product = { id: string; title: string; price: string };
// 1. Izolăm item-ul și îl protejăm cu memo
const ProductCard = memo(({ title, price, onPress }: Product & { onPress: (id: string) => void }) => {
return (
<Pressable style={styles.card}>
<Text style={styles.title}>{title}</Text>
<Text>{price}</Text>
</Pressable>
);
});
// 2. În listă folosim FlashList în loc de FlatList
export const ProductList = ({ items }: { items: Product[] }) => {
return (
<FlashList
data={items}
renderItem={({ item }) => (
<ProductCard id={item.id} title={item.title} price={item.price} onPress={() => {}} />
)}
estimatedItemSize={80}
keyExtractor={(item) => item.id}
/>
);
};
const styles = StyleSheet.create({
card: { padding: 16, height: 80, borderBottomWidth: 1, borderColor: '#ccc' },
title: { fontWeight: 'bold' }
});Anul trecut am preluat o aplicație de e-commerce cu vreo 12.000 de produse în catalog și un feed principal care gâfâia groaznic pe telefoane mid-range. Utilizatorii aveau drop-uri masive de frame-uri, ajungând la 20-25 FPS când derulau mai repede. Am rezolvat problema curățând optimizările premature inutile și înlocuind vechiul FlatList cu @shopify/flash-list.
Paranoia useMemo și useCallback
M-am săturat să văd codebase-uri unde fiecare funcție anonimă și fiecare obiect inline e îmbrăcat obsesiv în useCallback sau useMemo. În React Native, garbage collector-ul din engine-ul Hermes e surprinzător de rapid la alocări mici.
Dacă wrapping-ul te costă mai multă memorie și verificări de dependențe decât re-crearea unei funcții simple, ai pierdut startul. useMemo merită doar când faci operații grele — sortări de array-uri mari, filtrări pe mii de elemente sau parsări de JSON-uri masive. Pentru un style={{ marginTop: 10 }} sau o funcție const handlePress = () => navigation.navigate('Details'), un useCallback trântit la nimereală e pură pierdere de timp. Nu salvezi nicio randare dacă componenta copil oricum nu e wrap-uită în React.memo.
Re-renderings în liste: React.memo pus unde trebuie
Locul unde chiar explodează thread-ul de JS este lista scrollabilă. Dacă ai 50 de elemente vizibile și dai Like la un produs, de multe ori render-ul părintelui declanșează re-randarea tuturor celor 50 de copii, chiar dacă doar datele unuia singur s-au schimbat.
Aici React.memo salvează ziua. Rulează componenta de item printr-un React.memo și asigură-te că props-urile transmise sunt primitive sau referințe stabile. În proiectul menționat, am scăzut timpul de răspuns la tap de la 180ms la sub 16ms doar izolând corespunzător componenta cardului de produs.
Adio FlatList, bun venit FlashList
FlatList-ul nativ din React Native creează și distruge componente pe măsură ce scrollezi. Asta înseamnă un tăvălug continuu de alocări și de-alocări pe thread-ul de JS și peste bridge-ul nativ.
Cei de la Shopify au creat @shopify/flash-list, care folosește recilarea celulelor (cell recycling, similar cu RecyclerView din Android sau UICollectionView din iOS). În loc să distrugă componentele ieșite din ecran, le păstrează instanța și le reatribuie date noi. Am schimbat FlatList cu FlashList în mai puțin de o oră de refactoring.
Rezultatul? FPS-ul a urcat instant la 60 FPS stabil, iar consumul de RAM pe ecranul de listing s-a redus cu aproape 35%.
Trade-off-ul sincer? Dacă ai elemente cu înălțimi extrem de dinamice și imprevizibile, trebuie să fii foarte atent când setezi estimatedItemSize. Dacă pui o valoare complet greșită, utilizatorul va vedea mici "sărituri" de layout sau flickering în timpul scroll-ului rapid.
Oprește-te din împachetat totul în useCallback. Profilează mai întâi cu React DevTools sau Flipper, izolează componentele din liste cu React.memo și trece pe FlashList pentru orice ecran unde ai peste 20 de elemente.
Voi ce experiențe aveți cu FlashList pe producție? Ați dat de bug-uri bizare pe layout-uri mai complexe?