import React from 'react';
import { FlashList } from '@shopify/flash-list';
import { ProductCard, Product } from './ProductCard';
interface Props {
products: Product[];
}
export const ProductCatalog = ({ products }: Props) => {
return (
<FlashList
data={products}
renderItem={({ item }) => <ProductCard product={item} />}
estimatedItemSize={280}
keyExtractor={(item) => item.id}
overrideItemLayout={(layout, item) => {
// Oțional: ajustare dacă ai tipuri diferite de carduri
if (item.isFeatured) layout.size = 360;
}}
/>
);
};Problema de la care a plecat totul
Anul trecut lucram la un e-commerce local. Aveam un catalog cu peste 15.000 de produse, filtre dinamice și carduri destul de încărcate: imagini, badge-uri de discount și stoc live. Pe iPhone 13-le meu de teste totul mergea brici, 60 FPS fluide. Când am pus însă aplicația pe un Samsung A12 cu 3GB RAM — telefonul tipic pe care îl are o bună parte din baza de useri din RO —, dezastru. Scroll-ul agăța grav, iar din Perf Monitor vedeam cum JS FPS scădea dramatic la 12-18 frame-uri.
FlatList-ul nativ din React Native tinde să cedeze pe Android low-end pentru că unmount-ează și re-creează noduri React încontinuu. Irosește cicluri de CPU cu reconcilierea layout-ului pe thread-ul de JS, iar bridge-ul devine rapid gâtul de sticlă.
Trecerea la FlashList și numerele reale
Am decis să înlocuim FlatList cu @shopify/flash-list. Promisiunile din documentație sunau bine, dar aveam doza mea de scepticism. Spre deosebire de FlatList, FlashList folosește reciclare de view-uri pe bune (conceptul clasic de RecyclerView din Android nativ), în loc să distrugă și să creeze elemente la infinit.
După două ore de refactoring, am pornit din nou Perf Monitor pe Samsung-ul A12. Rezultatele au fost noaptea și ziua:
- FlatList clasic: UI FPS ~42, JS FPS ~18 la scroll rapid. Zone albe (blank space) vizibile de câte jumătate de secundă.
- FlashList: UI FPS 58-60, JS FPS 55-58. Zonele albe au dispărut aproape complet, chiar și la scroll agresiv.
Am obținut o experiență nativă pe cel mai ieftin telefon din birou, fără să fim nevoiți să regândim arhitectura de state a aplicației.
Unde se rupe filmul: Trade-off-uri sincere
Nu e totul numai lapte și miere. Când treci la FlashList, dai peste câteva capcane specifice de care te lovești la prima implementare:
estimatedItemSizee critic: Dacă pui o valoare aiurea (de exemplu pui 100px și cardul are în realitate 350px), o să ai flicker urât pe ecran și scroll jumpy. Trebuie să măsori dimensiunea medie reală din UI.- Reciclarea stării interne: Pentru că componentele se reciclează, dacă ai stare locală într-un card (de exemplu un
const [isLiked, setIsLiked] = useState(false)), când elementul iese din viewport și e reciclat pentru alt produs, starea aia va persista dacă nu e derivată direct din props sau resetată manual. - Layout-uri dinamice cu variații enorme: Dacă lista ta conține elemente cu înălțimi complet opuse (un banner de 400px urmat de un text de 20px), FlashList se chinuie la calculul offset-urilor.
Cum arată schimbarea în cod
Migrarea e simplă dacă ai deja un renderItem curat și izolat. În majoritatea cazurilor înlocuiești doar componenta și adaugi prop-ul de estimare.
Dacă ai o aplicație cu scroll infinit și mai mult de 20% din useri pe Android entry-level, optimizarea asta e probabil cel mai ieftin gain de performanță pe care îl poți bifa într-un sprint.
Voi ce folosiți în producție pe React Native în ultima vreme? Ați rămas pe FlatList tuns cu windowSize și maxToRenderPerBatch sau ați migrat deja pe FlashList?