import React, { useCallback } from 'react';
import { FlashList } from '@shopify/flash-list';
import { ProductCard } from './ProductCard';
interface Product {
id: string;
title: string;
}
export const ProductList = ({ products, onItemPress }: { products: Product[], onItemPress: (id: string) => void }) => {
// useCallback are sens aici pentru că ProductCard este învelit în React.memo
const renderItem = useCallback(({ item }: { item: Product }) => (
<ProductCard item={item} onPress={onItemPress} />
), [onItemPress]);
return (
<FlashList
data={products}
renderItem={renderItem}
estimatedItemSize={120} // Valoare critică pentru performanța FlashList
keyExtractor={(item) => item.id}
/>
);
};Să fim sinceri: majoritatea aplicațiilor React Native încep să agațe fix când utilizatorul are mai mare nevoie de ele, adică la scroll. Am văzut zeci de proiecte unde s-a dat cu useCallback și useMemo peste tot, ca un fel de mir miraculos, iar rezultatul a fost exact pe dos. Hai să vedem unde e de fapt gâtul de pâlnie și cum rezolvăm asta cu cifrele pe masă.
Obsesia pentru useMemo și useCallback: când faci mai mult rău
Fiecare useCallback înseamnă o alocare suplimentară de memorie și o verificare în plus la fiecare render. Dacă ai o funcție simplă de onPress care doar deschide o ruteră, să o pui în useCallback e pierdere de timp.
La o aplicație de livrări cu vreo 12.000 de produse active, am găsit peste 50 de useCallback-uri inutile care doar mâncau memorie la inițializare. După ce am curățat 80% din ele, garbage collector-ul a început să respire și am câștigat cam 5% la timpul de startup pe telefoane ieftine.
Când le folosești? Doar când transmiți acea funcție ca prop către un copil care este învelit în React.memo și vrei să eviți re-renderarea acelui copil. Altfel, n-are sens.
React.memo nu este gratuit
React.memo face o comparație superficială (shallow comparison) a prop-urilor. Dacă prop-urile tale se schimbă la fiecare render (de exemplu, trimiți un obiect inline de tipul style={{ margin: 10 }}), comparația va da mereu false. Copilul se va re-rendera oricum, iar tu ai plătit și taxa de comparație degeaba.
Folosește React.memo pe componentele din liste, dar asigură-te că prop-urile sunt primitive sau memoizate corect la nivel de părinte. Dacă componenta ta se randează rapid, adesea e mai ieftin să o lași să se re-randereze decât să o compari manual la fiecare ciclu.
FlatList vs FlashList: Aici e adevărata magie
Dacă ai liste lungi, clasicul FlatList din React Native te omoară pe Android. FlatList distruge componentele care ies din ecran și creează altele noi când dai scroll înapoi. Asta înseamnă alocări masive de memorie pe JS thread, ceea ce duce la acele secunde în care ecranul rămâne alb la scroll rapid.
Cei de la Shopify au scris FlashList, care reciclează celulele (exact cum face RecyclerView pe Android nativ). Am migrat un feed complex acum un an la FlashList și am sărit instant de la 32 FPS la un stabil 59 FPS pe un Samsung A12 destul de obosit.
Trade-off-ul? Trebuie să îi dai un estimatedItemSize. Dacă estimarea ta e complet greșită, lista o să sară urât la scroll (layout shifts). De asemenea, dacă ai itemi cu dimensiuni extrem de dinamice (de la 50px la 600px), FlashList s-ar putea să mai dea rateuri la calculul scroll-ului. Dar pentru 90% din cazuri, e net superior.
Nu optimizați din ochi. Puneți mâna pe Profiler-ul din React DevTools, măsurați unde scade FPS-ul și abia atunci schimbați FlatList-ul sau puneți memo-uri. Voi ce probleme de scroll ați avut pe Android și cum le-ați dat de cap?