import React, { memo, useCallback } from 'react';
import { Text, TouchableOpacity, StyleSheet } from 'react-native';
import { FlashList } from '@shopify/flash-list';
type Transaction = { id: string; amount: number; title: string };
// Componentă memoizată corect
const TransactionRow = memo(({ item, onPress }: { item: Transaction; onPress: (id: string) => void }) => {
return (
<TouchableOpacity style={styles.row} onPress={() => onPress(item.id)}>
<Text>{item.title}</Text>
<Text>{item.amount} RON</Text>
</TouchableOpacity>
);
});
export const TransactionFeed = ({ data }: { data: Transaction[] }) => {
// Referință stabilă pentru onPress
const handlePress = useCallback((id: string) => {
console.log('Selectat:', id);
}, []);
return (
<FlashList
data={data}
renderItem={({ item }) => <TransactionRow item={item} onPress={handlePress} />}
estimatedItemSize={64}
keyExtractor={(item) => item.id}
/>
);
};
const styles = StyleSheet.create({
row: { height: 64, flexDirection: 'row', justifyContent: 'space-between', padding: 16 }
});Am văzut prea multe aplicații de React Native care agătau la scroll pe telefoane mid-range doar pentru că echipa a trântit useCallback peste tot fără să înțeleagă ce face. În postarea asta trecem prin ce mută cu adevărat acul pe performanță când ai feed-uri mari și ce e doar boilerplate inutil care îți încarcă codul.
Trinitatea memo, useMemo și useCallback: Când chiar ajută?
Există o concepție greșită printre devii de nivel intermediar că dacă pui useCallback pe o funcție, componenta nu se mai re-randează. Fals. useCallback doar păstrează aceeași referință a funcției între renderări. Dacă componenta copil care primește acea funcție ca prop NU este împachetată în React.memo, ai muncit degeaba. Copilul se va re-randa oricum când se schimbă starea părintelui.
Am avut cazul la un proiect de fintech cu 12k utilizatori activi zilnic. Aveam un ecran cu un feed de tranzacții live care primea update-uri pe WebSocket de vreo 3 ori pe secundă. Tot ecranul clipea și JS thread-ul stătea blocat la 15-20 FPS pe un Android mai ieftin. Soluția n-a fost să punem memo peste tot. Am extras rândul de tranzacție într-o componentă separată, am pus React.memo pe ea cu o funcție de comparare personalizată și am izolat starea locală. Rezultatul? Trecere de la 18 FPS la un tăcut 60 FPS constante.
useMemo e util doar dacă ai calcule grele (de exemplu, filtrarea sau sortarea unei liste de 2.000 de iteme pe client). Dacă doar formatezi o dată de naștere cu date-fns, costul de overhead pentru useMemo e adesea mai mare decât calculul în sine.
FlashList vs FlatList: Reciclare vs Distrugere
Dacă ai o listă cu mai mult de 50-100 de elemente și folosești FlatList din React Native core, pierzi performanță masiv. FlatList funcționează prin crearea și distrugerea de views nativ pe măsură ce scrollezi. Când dai scroll rapid (fast swipe), Bridge-ul sau JSI-ul devine gât de sticlă pentru că se cer sute de unmount-uri și mount-uri simultan.
Cei de la Shopify au scos FlashList tocmai din motivul ăsta. El folosește un concept clasic din Android/iOS (RecyclerView / UICollectionView): reciclează componentele existente și le schimbă doar datele interne din JS.
Să fim sinceri și cu trade-off-ul: FlashList nu e o magie neagră fără costuri. Trebuie să îi furnizezi un estimatedItemSize cât mai aproape de realitate. Dacă elementele tale au înălțimi total dinamice (de la 50px la 600px), poți avea mici jump-uri vizuale la scroll rapid. Totuși, la o listă de 5.000 de produse, consumul de memorie scade cu până la 40% și dispare complet ecranul alb temporar.
Ce reguli urmez acum în producție
- Nu pune
useCallbackdacă funcția e transmisă către un element nativ simplu (cum e<TouchableOpacity onPress={...} />). E o pierdere de timp. - Folosește
FlashListca default pentru orice listă care poate trece de 20 de articole. - Folosește React DevTools Profiler înainte să optimizezi. Caută componentele marcate cu roșu care se re-randază fără ca prop-urile lor reale să se fi schimbat.
Voi la ce strategii apelați când vă pică frame-rate-ul pe Android?