import React, { memo, useCallback } from 'react';
import { Text, TouchableOpacity, View } from 'react-native';
import { FlashList } from '@shopify/flash-list';
type ItemProps = { id: string; title: string; onSelect: (id: string) => void };
// 1. React.memo oprește re-renderarea dacă props-urile sunt identice
const ListItem = memo(({ id, title, onSelect }: ItemProps) => {
return (
<TouchableOpacity onPress={() => onSelect(id)} style={{ padding: 16 }}>
<Text>{title}</Text>
</TouchableOpacity>
);
});
export const ProductList = ({ data }: { data: Array<{ id: string; title: string }> }) => {
// 2. useCallback păstrează referința stabilă pentru ListItem-ul memoizat
const handleSelect = useCallback((id: string) => {
console.log('Selected product:', id);
}, []);
return (
<FlashList
data={data}
renderItem={({ item }) => (
<ListItem id={item.id} title={item.title} onSelect={handleSelect} />
)}
estimatedItemSize={70}
keyExtractor={(item) => item.id}
/>
);
};Salutare! Am văzut din ce în ce mai des PR-uri unde fiecare funcție e îmbrăcată automat în useCallback și orice obiect trece prin useMemo. Să fim sinceri: în 80% din cazuri doar adaugi overhead de memorie și îngreunezi citirea codului.
Anul trecut lucram la un e-commerce local cu peste 15k produse în catalog. În ecranele de listă, thread-ul de JS agăța masiv la scroll – aveam drop-uri de la 60 FPS la 20-22 FPS pe dispozitive mid-range de Android. Prima reacție a echipei a fost: "hai să punem useCallback pe toate handlerele de onPress". Rezultatul? Nicio diferență sesizabilă.
useCallback nu face o funcție mai rapidă. El doar păstrează aceeași referință în memorie între re-renderări. Dacă componenta copil care primește acea funcție NU este învelită în React.memo, copilul se va re-rendera ORICUM pentru că părintele s-a re-renderat. Ai plătit prețul de hook degeaba.
Când chiar folosești memo, useCallback și useMemo
Iată regula mea simplă după mai bine de 10 ani de dev:
React.memo: Îl pui pe componente vizuale grele sau pe elemente de listă care se repetă de sute de ori (ex:ProductCard). Dacă o componentă e simplă (unTextși unIcon), costul diferențierii props-urilor făcut dememopoate fi mai mare decât re-renderarea ei nativă.useCallback: Îl folosești EXCLUSIV când pasezi callback-ul către o componentă copil optimizată deja cuReact.memosau când funcția este o dependență într-unuseEffect.useMemo: Util pentru calcule grele (filtrări, sortări pe array-uri de mii de obiecte). Dacă doar faci un.map()simplu pe 10 elemente,useMemoe inutil.
La proiectul menționat, aveam carduri complexe de produs. Doar când am pus React.memo pe ProductCard ȘI useCallback pe funcția de onAddToCart, re-renderările inutile la scroll au scăzut cu aproape 70%.
FlatList vs FlashList: De la 22 la 60 FPS
Toată lumea știe că FlatList din React Native folosește VirtualizedList. Problema e că FlatList nu reciclează componentele native, ci le distruge și le recreează la scroll rapid. Pe un Android ieftin, asta înseamnă spații albe și vizibil lag.
Am migrat lista principală către @shopify/flash-list. Pentru că FlashList reciclează views-urile native exact ca un RecyclerView pe Android sau UICollectionView pe iOS, timpul de render pe JS thread a scăzut dramatic. Am economisit cam 30% memorie RAM și am scos FPS-ul din zona roșie direct la 60 constant.
Trade-off-ul sincer cu FlashList:
FlashList e excelent, dar necesită proprietatea estimatedItemSize. Dacă înălțimea cardurilor variază extrem de mult și e greu de estimat, vei avea sărituri urâte la scroll (layout shifts). În cazuri cu layout-uri imprevizibile, un FlatList bine reglat din windowSize și maxToRenderPerBatch poate fi mai stabil vizual.
Voi cum abordați profiling-ul pe React Native? Folosiți Profiler-ul din React DevTools sau mergeți direct pe Flipper și Perf Monitor-ul nativ?