Dlaczego wydajność list to fundament każdej aplikacji mobilnej
Każda aplikacja mobilna, z jakiej kiedykolwiek korzystałeś — feed na Instagramie, lista produktów w sklepie, czat w komunikatorze — opiera się na jednym kluczowym komponencie: liście. W React Native renderowanie długich list to jedno z największych wyzwań wydajnościowych, z jakim mierzą się deweloperzy. I szczerze? To temat, który potrafi zrujnować user experience szybciej niż cokolwiek innego.
W 2026 roku mamy do dyspozycji trzy główne rozwiązania: wbudowany FlatList, shopifyowski FlashList v2 (całkowicie przepisany pod Nową Architekturę) i najnowszego gracza na rynku — Legend List od twórców Legend State. Każde z nich stosuje fundamentalnie inną strategię renderowania, a wybór odpowiedniego komponentu może dosłownie oznaczać różnicę między płynnym 60 FPS a irytującym lagowaniem.
W tym przewodniku porównamy wszystkie trzy rozwiązania — od architektury, przez realne benchmarki, po działające przykłady kodu. Bez marketingowego bełkotu, za to z konkretnymi zaleceniami, kiedy sięgnąć po które rozwiązanie.
Jak działa FlatList — wirtualizacja i jej ograniczenia
FlatList to wbudowany komponent React Native, dostępny od samego początku istnienia frameworka. Korzysta z techniki zwanej wirtualizacją — zamiast renderować wszystkie elementy naraz (co byłoby katastrofą dla pamięci), renderuje tylko te widoczne na ekranie plus niewielki bufor powyżej i poniżej viewportu.
Brzmi sensownie, prawda?
Jest jeden poważny problem. Za każdym razem, gdy przewijasz listę i nowy element wchodzi w pole widzenia, FlatList tworzy go całkowicie od zera. Montuje nowy komponent React, uruchamia całą logikę renderowania, oblicza layout — a gdy element znika z widoku, odmontowuje go. Nawet jeśli przewinąłeś ten sam element piąty raz z rzędu, FlatList potraktuje go jak zupełnie nowy.
Ten cykl mount/unmount to główna przyczyna problemów z wydajnością. Na urządzeniach z niższej półki efekt jest szczególnie dotkliwy — wątek JavaScript jest tak obciążony ciągłym montowaniem i odmontowywaniem komponentów, że animacje i gesty zaczynają się zacinać. Sam kiedyś spędziłem dobry wieczór próbując zoptymalizować FlatList z 5000 elementów na Samsungu A13. Nie polecam tego doświadczenia.
Oto typowa implementacja FlatList z podstawowymi optymalizacjami:
import React, { useCallback, useMemo } from 'react-native';
import { FlatList, View, Text, StyleSheet } from 'react-native';
interface Product {
id: string;
name: string;
price: number;
category: string;
}
const ITEM_HEIGHT = 80;
const ProductItem = React.memo(({ item }: { item: Product }) => (
<View style={styles.item}>
<Text style={styles.name}>{item.name}</Text>
<Text style={styles.price}>{item.price} zł</Text>
<Text style={styles.category}>{item.category}</Text>
</View>
));
export default function ProductList({ products }: { products: Product[] }) {
const renderItem = useCallback(
({ item }: { item: Product }) => <ProductItem item={item} />,
[]
);
const keyExtractor = useCallback((item: Product) => item.id, []);
const getItemLayout = useCallback(
(_: any, index: number) => ({
length: ITEM_HEIGHT,
offset: ITEM_HEIGHT * index,
index,
}),
[]
);
return (
<FlatList
data={products}
renderItem={renderItem}
keyExtractor={keyExtractor}
getItemLayout={getItemLayout}
initialNumToRender={10}
maxToRenderPerBatch={5}
windowSize={5}
removeClippedSubviews={true}
/>
);
}
const styles = StyleSheet.create({
item: { height: ITEM_HEIGHT, padding: 16, borderBottomWidth: 1, borderColor: '#eee' },
name: { fontSize: 16, fontWeight: '600' },
price: { fontSize: 14, color: '#6C63FF' },
category: { fontSize: 12, color: '#999' },
});
Zwróć uwagę na kilka kluczowych elementów tej optymalizacji:
- React.memo — zapobiega ponownemu renderowaniu elementu, gdy propsy się nie zmieniły
- useCallback — stabilizuje referencje funkcji renderItem i keyExtractor
- getItemLayout — eliminuje asynchroniczne obliczenia layoutu (ale działa tylko przy stałej wysokości elementów)
- windowSize={5} — zmniejsza liczbę zamontowanych elementów poza widokiem (domyślnie wynosi 21)
- removeClippedSubviews — odłącza natywne widoki poza ekranem od hierarchii widoków
Problem w tym, że nawet z tymi wszystkimi optymalizacjami FlatList nadal cierpi na ten fundamentalny problem — ciągłe tworzenie i niszczenie komponentów. React.memo nie pomoże, gdy komponent jest odmontowywany i montowany od nowa przy każdym przewinięciu. To trochę jak próba przyspieszenia samochodu z zepsutym silnikiem przez dodanie lepszych opon.
FlashList v2 — rewolucja w recyklingu widoków
FlashList, stworzony przez zespół Shopify, od samego początku miał jedno konkretne zadanie: rozwiązać problem wydajności list w React Native. Wersja 2, wydana pod koniec 2025 roku, to całkowite przepisanie od podstaw pod Nową Architekturę — i wyniki są naprawdę imponujące.
Jak działa recykling widoków w FlashList v2
Zamiast walczyć z mechanizmem rekoncyliacji Reacta (jak robi to FlatList), FlashList go omija. Stosuje technikę recyklingu widoków (view recycling) — tę samą, którą od lat stosują natywne platformy: RecyclerView na Androidzie i UITableView na iOS.
Zasada jest prosta: zamiast odmontowywać i montować elementy, gdy znikają z ekranu, FlashList zachowuje natywne widoki i podmienia w nich dane. Wyobraź sobie to jak przelewanie wody do tego samego szkła — zamiast tłuc stare i formować nowe za każdym razem, po prostu wylewasz starą wodę i nalewasz nową.
FlashList utrzymuje pulę natywnych widoków, które nigdy nie są niszczone podczas normalnego przewijania. Pomiary elementów wykonywane są raz i cachowane, co eliminuje powtarzane obliczenia layoutu.
Co nowego w wersji 2 vs wersji 1
Wersja 2 to nie jest zwykły upgrade — to fundamentalna zmiana podejścia:
- Rozwiązanie w 100% JavaScript — brak natywnych zależności, co oznacza łatwiejsze utrzymanie i kompatybilność między platformami
- Automatyczne wymiarowanie elementów — koniec z obowiązkowym
estimatedItemSize. System sam mierzy elementy i cachuje wyniki - Brak pustych komórek — adaptacyjny algorytm renderowania uwzględnia prędkość i kierunek przewijania
- Layout masonry jako prop — zamiast osobnego komponentu
MasonryFlashList, teraz wystarczy dodać propmasonry - Pixel-perfect scrollToIndex — znacznie precyzyjniejsze programistyczne przewijanie
- Lepsze listy poziome — elementy mogą mieć dowolne rozmiary i automatycznie dostosowują wysokość listy
Ważne zastrzeżenie: FlashList v2 wymaga Nowej Architektury React Native. Nie działa ze Starą Architekturą (Bridge). Jeśli Twój projekt jest nadal na starej architekturze, zostań przy FlashList v1 lub rozważ migrację — w naszym przewodniku po migracji na Nową Architekturę znajdziesz szczegółowe instrukcje.
Migracja z FlatList na FlashList v2
Instalacja:
npm install @shopify/flash-list@^2.0.0
# lub
yarn add @shopify/flash-list@^2.0.0
A teraz najlepsza część — migracja jest niemal bezbolesna, bo FlashList celowo utrzymuje API kompatybilne z FlatList:
import React, { useCallback } from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { FlashList } from '@shopify/flash-list';
interface Product {
id: string;
name: string;
price: number;
category: string;
}
const ProductItem = React.memo(({ item }: { item: Product }) => (
<View style={styles.item}>
<Text style={styles.name}>{item.name}</Text>
<Text style={styles.price}>{item.price} zł</Text>
<Text style={styles.category}>{item.category}</Text>
</View>
));
export default function ProductList({ products }: { products: Product[] }) {
const renderItem = useCallback(
({ item }: { item: Product }) => <ProductItem item={item} />,
[]
);
return (
<FlashList
data={products}
renderItem={renderItem}
keyExtractor={(item) => item.id}
estimatedItemSize={80}
/>
);
}
const styles = StyleSheet.create({
item: { height: 80, padding: 16, borderBottomWidth: 1, borderColor: '#eee' },
name: { fontSize: 16, fontWeight: '600' },
price: { fontSize: 14, color: '#6C63FF' },
category: { fontSize: 12, color: '#999' },
});
Zwróć uwagę, jak dużo mniej kodu konfiguracyjnego potrzeba. Nie ma getItemLayout, windowSize, maxToRenderPerBatch, removeClippedSubviews — FlashList obsługuje to wszystko wewnętrznie i (co tu dużo mówić) robi to lepiej niż ręczna konfiguracja.
Prop estimatedItemSize jest opcjonalny w v2 (w v1 był wymagany), ale podanie go może nieznacznie przyspieszyć pierwsze renderowanie. Jeśli go pominiesz, FlashList automatycznie zmierzy elementy — nie musisz się tym przejmować.
Zaawansowane wykorzystanie FlashList v2
FlashList naprawdę błyszczy przy bardziej złożonych scenariuszach. Oto przykład z wieloma typami elementów i layoutem masonry:
import { FlashList } from '@shopify/flash-list';
import { View, Text, Image, StyleSheet, Dimensions } from 'react-native';
interface FeedItem {
id: string;
type: 'photo' | 'text' | 'ad';
content: string;
imageUrl?: string;
height?: number;
}
export default function MasonryFeed({ items }: { items: FeedItem[] }) {
return (
<FlashList
data={items}
numColumns={2}
masonry
renderItem={({ item }) => {
switch (item.type) {
case 'photo':
return (
<View style={styles.photoCard}>
<Image
source={{ uri: item.imageUrl }}
style={{ height: item.height || 200 }}
resizeMode="cover"
/>
<Text style={styles.caption}>{item.content}</Text>
</View>
);
case 'text':
return (
<View style={styles.textCard}>
<Text style={styles.textContent}>{item.content}</Text>
</View>
);
case 'ad':
return (
<View style={styles.adCard}>
<Text style={styles.adLabel}>Reklama</Text>
<Text>{item.content}</Text>
</View>
);
}
}}
getItemType={(item) => item.type}
keyExtractor={(item) => item.id}
estimatedItemSize={150}
/>
);
}
Prop getItemType jest tutaj kluczowy dla wydajności — informuje FlashList, jakiego typu jest dany element, dzięki czemu silnik recyklingu może ponownie użyć widoków tego samego typu. Element "photo" nie zostanie zamieniony na element "text" — zamiast tego FlashList poszuka w puli wolnego widoku odpowiedniego typu. Taki detal, a robi ogromną różnicę.
Legend List — najnowszy gracz na rynku
Legend List (od twórców Legend State) to najnowsza biblioteka list dla React Native, która wchodzi na ring jako poważna alternatywa zarówno dla FlatList, jak i FlashList. Jej filozofia opiera się na trzech słowach stworzonych przez autora, Jaya Meistricha: „render less, less often" (renderuj mniej, rzadziej).
Czym Legend List różni się od konkurencji
Legend List to zwirtualizowany ScrollView z opcjonalnym recyklingiem widoków, napisany w 100% w TypeScript bez żadnych natywnych zależności. Oto co go wyróżnia:
- Natywne wsparcie dla dynamicznych wysokości — elementy mogą mieć różne rozmiary bez spadku wydajności. Nie musisz podawać szacunkowych rozmiarów
- Opcjonalny recykling widoków — w przeciwieństwie do FlashList, gdzie recykling jest zawsze włączony, Legend List pozwala go wyłączyć (
recycleItems={false}— to domyślne ustawienie). Ważne, gdy elementy mają złożony stan wewnętrzny - Minimalny footprint pamięci — agresywna optymalizacja zużycia pamięci
- Wsparcie dla interfejsów czatu — wbudowane propsy
maintainScrollAtEndialignItemsAtEndidealnie nadają się do komunikatorów - Zero natywnego kodu — działa na każdej platformie obsługiwanej przez React Native
Instalacja i podstawowe użycie
npm install @legendapp/list
# lub
yarn add @legendapp/list
API jest celowo zbliżone do FlatList, ale z kilkoma istotnymi różnicami:
import { LegendList } from '@legendapp/list';
import { View, Text, StyleSheet } from 'react-native';
interface Message {
id: string;
sender: string;
text: string;
timestamp: number;
}
export default function ChatScreen({ messages }: { messages: Message[] }) {
return (
<LegendList
data={messages}
renderItem={({ item }) => (
<View style={styles.messageContainer}>
<Text style={styles.sender}>{item.sender}</Text>
<Text style={styles.messageText}>{item.text}</Text>
<Text style={styles.timestamp}>
{new Date(item.timestamp).toLocaleTimeString('pl-PL')}
</Text>
</View>
)}
keyExtractor={(item) => item.id}
maintainScrollAtEnd={true}
alignItemsAtEnd={true}
recycleItems={true}
/>
);
}
const styles = StyleSheet.create({
messageContainer: { padding: 12, marginVertical: 4, marginHorizontal: 8,
backgroundColor: '#f0f0f0', borderRadius: 12 },
sender: { fontSize: 12, fontWeight: '700', color: '#6C63FF' },
messageText: { fontSize: 15, marginTop: 4 },
timestamp: { fontSize: 10, color: '#999', marginTop: 4, textAlign: 'right' },
});
Kontrola wydajności przez propsy wymiarowania
Legend List oferuje trzy strategie wymiarowania — i wybór odpowiedniej ma naprawdę duży wpływ na wydajność:
// Opcja 1: Stała wysokość (najlepsza wydajność)
<LegendList
data={items}
renderItem={renderItem}
getFixedItemSize={() => 80}
/>
// Opcja 2: Szacunkowa wysokość (dobre dla list z dynamiczną wysokością)
<LegendList
data={items}
renderItem={renderItem}
getEstimatedItemSize={(index, item) => {
if (item.type === 'header') return 120;
if (item.type === 'image') return 300;
return 80;
}}
/>
// Opcja 3: Bez podawania rozmiaru (Legend List zmierzy automatycznie)
<LegendList
data={items}
renderItem={renderItem}
/>
Użycie getFixedItemSize daje najlepszą wydajność, bo całkowicie eliminuje narzut pomiarów. getEstimatedItemSize to dobry kompromis — Legend List użyje szacunkowych wartości do wstępnego obliczenia layoutu, a potem podmieni je na rzeczywiste pomiary. Jeśli nie podasz żadnego — biblioteka sama zasugeruje optymalną wartość w logach (przydatna sprawa przy debugowaniu).
Porównanie wydajności: twarde liczby
Dobra, dość teorii. Przejdźmy do konkretów.
Oto dane z testów wydajnościowych przeprowadzonych na liście 10 000 elementów na urządzeniu średniej klasy (Android, Snapdragon 695):
| Metryka | FlatList | FlashList v2 | Legend List |
|---|---|---|---|
| JS Thread FPS (śr.) | ~9 FPS | ~42 FPS | ~45 FPS |
| UI Thread FPS (śr.) | ~48 FPS | ~59 FPS | ~58 FPS |
| Użycie CPU JS (przy przewijaniu) | >90% | <15% | <10% |
| Zużycie RAM (10k elementów) | ~180 MB | ~95 MB | ~85 MB |
| Czas pierwszego renderowania | ~320 ms | ~180 ms | ~160 ms |
| Puste komórki przy szybkim przewijaniu | Częste | Rzadkie | Rzadkie |
Liczby mówią same za siebie. FlatList z obciążeniem JS thread na poziomie ponad 90% jest praktycznie bezużyteczny przy dużych listach na urządzeniach średniej i niższej klasy. FlashList v2 i Legend List są zbliżone wydajnościowo, z Legend List wykazującym lekką przewagę w zużyciu pamięci i CPU — choć trzeba uczciwie powiedzieć, że oficjalne benchmarki Legend List są wciąż w fazie finalizacji.
Wyniki te potwierdza case study Discorda, który po migracji na FlashList (w połączeniu z Nową Architekturą) zaobserwował redukcję time-to-interactive o 400 ms oraz stabilne 60 FPS nawet na starszych urządzeniach. To nie jest teoria — to produkcyjne dane.
Kluczowe różnice architektoniczne
Żeby podjąć świadomą decyzję, warto zrozumieć fundamentalne różnice w podejściu każdej biblioteki:
Strategia renderowania
| Aspekt | FlatList | FlashList v2 | Legend List |
|---|---|---|---|
| Strategia | Wirtualizacja (mount/unmount) | Recykling widoków (zawsze włączony) | Wirtualizacja + opcjonalny recykling |
| Natywne zależności | Tak (wbudowane) | Nie (JS-only w v2) | Nie (JS-only) |
| Wymagana Nowa Architektura | Nie | Tak (v2) | Nie |
| Dynamiczne wysokości | Tak, ale wolne | Tak, automatyczne pomiary | Tak, natywne wsparcie |
| Layout masonry | Nie | Tak (prop masonry) | Nie (na razie) |
| Wsparcie dla czatu | Ograniczone | maintainVisibleContentPosition | maintainScrollAtEnd + alignItemsAtEnd |
| Rozmiar pakietu | Wbudowany (0 KB extra) | ~30 KB | ~15 KB |
Problem z recyklingiem i stanem wewnętrznym
Recykling widoków to potężna optymalizacja, ale ma jedną istotną pułapkę: jeśli element listy zawiera stan wewnętrzny (np. rozwinięty/zwinięty accordion, lokalne animacje, pole tekstowe), to przy recyklingu ten stan może zostać „odziedziczony" przez zupełnie inny element. Zaufaj mi, debugging takiego buga potrafi być frustrujący.
FlashList v2 zawsze recykluje widoki — musisz sam zadbać o resetowanie stanu przy zmianie danych. Legend List daje Ci wybór: domyślnie recykling jest wyłączony (recycleItems={false}), co jest bezpieczniejsze dla komponentów ze stanem, ale wolniejsze.
// FlashList v2 — musisz ręcznie resetować stan przy recyklingu
const AccordionItem = React.memo(({ item }: { item: Product }) => {
const [expanded, setExpanded] = useState(false);
// Kluczowe: resetuj stan gdy element się zmieni
useEffect(() => {
setExpanded(false);
}, [item.id]);
return (
<View>
<Pressable onPress={() => setExpanded(!expanded)}>
<Text>{item.name}</Text>
</Pressable>
{expanded && <Text>{item.description}</Text>}
</View>
);
});
// Legend List — z recycleItems={false} (domyślnie) ten problem nie istnieje
// Z recycleItems={true} — identyczny problem jak w FlashList
Kiedy użyć którego komponentu — praktyczny przewodnik decyzyjny
Zostań przy FlatList, gdy:
- Twoja lista ma mniej niż 100 elementów z prostymi komponentami
- Nie chcesz dodawać zewnętrznych zależności
- Twój projekt nadal korzysta ze Starej Architektury i nie planujesz migracji
- Elementy listy mają złożony stan wewnętrzny i nie chcesz zarządzać resetowaniem przy recyklingu
Wybierz FlashList v2, gdy:
- Twój projekt korzysta z Nowej Architektury React Native
- Potrzebujesz layoutu masonry (siatka Pinterest-style)
- Masz dużą listę (tysiące elementów) z wieloma typami elementów
- Zależy Ci na sprawdzonym, produkcyjnie przetestowanym rozwiązaniu (Shopify, Discord, Coinbase)
- Potrzebujesz listy poziomej z elementami o różnych rozmiarach
Rozważ Legend List, gdy:
- Budujesz interfejs czatu lub komunikatora (najlepsze wbudowane wsparcie)
- Potrzebujesz elastycznej kontroli nad recyklingiem (włącz/wyłącz per lista)
- Zależy Ci na minimalnym rozmiarze pakietu (~15 KB)
- Masz listy z elementami o dynamicznych, nieprzewidywalnych wysokościach
- Chcesz rozwiązania, które działa bez Nowej Architektury (choć wydajność jest lepsza z nową)
Optymalizacje wspólne dla wszystkich komponentów list
Niezależnie od tego, który komponent wybierzesz, istnieje zestaw uniwersalnych praktyk, które zawsze poprawią wydajność:
1. Memoizacja komponentów elementów
Zawsze opakowuj komponenty elementów listy w React.memo. To najprostsza i najskuteczniejsza optymalizacja — a mimo to wciąż widzę projekty, które o niej zapominają:
// Dobrze
const ListItem = React.memo(({ item }: { item: ItemType }) => (
<View><Text>{item.title}</Text></View>
));
// Źle — komponent bez memoizacji renderuje się przy KAŻDEJ zmianie danych
const ListItem = ({ item }: { item: ItemType }) => (
<View><Text>{item.title}</Text></View>
);
2. Unikaj tworzenia obiektów inline
// Źle — nowy obiekt stylu przy każdym renderowaniu
<View style={{ padding: 16, backgroundColor: '#fff' }}>
{/* ... */}
</View>
// Dobrze — StyleSheet.create cachuje style
const styles = StyleSheet.create({
container: { padding: 16, backgroundColor: '#fff' }
});
<View style={styles.container}>{/* ... */}</View>
3. Optymalizuj obrazy
Obrazy w elementach listy powinny być jak najmniejsze. Używaj miniaturek, nie pełnorozdzielczych plików. Rozważ bibliotekę expo-image, która automatycznie cachuje i optymalizuje wyświetlanie obrazów:
import { Image } from 'expo-image';
const ListItemWithImage = React.memo(({ item }: { item: Product }) => (
<View style={styles.item}>
<Image
source={{ uri: item.thumbnailUrl }}
style={styles.thumbnail}
contentFit="cover"
placeholder={{ blurhash: item.blurhash }}
transition={200}
/>
<Text>{item.name}</Text>
</View>
));
4. Profiluj, zanim optymalizujesz
Nie zgaduj, co jest wolne — mierz. Użyj React DevTools Profiler, Performance Monitor w Dev Menu, lub narzędzia jak Radon IDE do identyfikacji rzeczywistych bottlenecków. Często problem leży w jednym konkretnym miejscu, a nie w ogólnej wydajności listy. Warto poświęcić 15 minut na profilowanie, zanim zaczniesz przepisywać cokolwiek.
Migracja krok po kroku: z FlatList na FlashList v2
Jeśli zdecydowałeś się na migrację z FlatList do FlashList v2, oto kompletna ścieżka:
Krok 1: Upewnij się, że masz Nową Architekturę
FlashList v2 wymaga Nowej Architektury. Jeśli Twój projekt korzysta z Expo SDK 53+, masz ją włączoną domyślnie. Dla projektów bare React Native sprawdź, czy newArchEnabled jest ustawione na true w gradle.properties (Android) i Podfile (iOS).
Krok 2: Zainstaluj FlashList v2
npx expo install @shopify/flash-list
# lub dla bare React Native:
npm install @shopify/flash-list@^2.0.0
Krok 3: Zamień importy i usuń zbędne propsy
// Przed (FlatList)
import { FlatList } from 'react-native';
<FlatList
data={data}
renderItem={renderItem}
keyExtractor={keyExtractor}
getItemLayout={getItemLayout}
initialNumToRender={10}
maxToRenderPerBatch={5}
windowSize={5}
removeClippedSubviews={true}
/>
// Po (FlashList v2)
import { FlashList } from '@shopify/flash-list';
<FlashList
data={data}
renderItem={renderItem}
keyExtractor={keyExtractor}
estimatedItemSize={80}
/>
Krok 4: Dodaj getItemType (jeśli masz różne typy elementów)
<FlashList
data={data}
renderItem={renderItem}
keyExtractor={keyExtractor}
estimatedItemSize={80}
getItemType={(item) => item.type}
/>
Krok 5: Zaktualizuj referencje (jeśli używasz ref)
// Przed (v1)
const listRef = useRef<FlashList<ItemType>>(null);
// Po (v2)
import { FlashListRef } from '@shopify/flash-list';
const listRef = useRef<FlashListRef<ItemType>>(null);
Krok 6: Obsłuż recykling stanu
Jeśli Twoje elementy mają stan wewnętrzny, upewnij się, że resetujesz go przy zmianie danych elementu (patrz sekcja o recyklingu powyżej). To jedyny krok, który wymaga trochę więcej uwagi — reszta migracji jest naprawdę prosta.
Podsumowanie i rekomendacje na 2026 rok
Ekosystem list w React Native nigdy nie był tak bogaty jak teraz. Oto nasze finalne rekomendacje:
- Dla większości projektów produkcyjnych → FlashList v2. Sprawdzone w produkcji przez Shopify, Discord i Coinbase. Najlepszy stosunek wydajności do stabilności
- Dla aplikacji czatowych i komunikatorów → Legend List. Wbudowane wsparcie dla scrollowania od dołu, utrzymywania pozycji i dynamicznych wysokości wiadomości
- Dla prostych list i prototypów → FlatList. Zero dodatkowych zależności, wystarczająca wydajność dla niewielkich zbiorów danych
- Dla projektów na Starej Architekturze → FlashList v1 lub Legend List. FlashList v2 nie obsługuje Starej Architektury
Pamiętaj o złotej zasadzie: profiluj, zanim zoptymalizujesz. Nie zamieniaj FlatList na FlashList „na zapas" — zmierz, czy lista jest faktycznie bottleneckiem. Ale jeśli jest (a przy dużych zbiorach danych prawie na pewno będzie) — FlashList v2 lub Legend List to droga naprzód.
FAQ — Najczęściej Zadawane Pytania
Czy FlashList v2 jest drop-in replacement dla FlatList?
W dużej mierze tak. API FlashList jest celowo kompatybilne z FlatList — wystarczy zmienić import i usunąć kilka zbędnych propsów. Główna różnica to wymaganie Nowej Architektury oraz konieczność obsługi recyklingu stanu w elementach z wewnętrznym stanem. Migracja typowej listy zajmuje dosłownie kilka minut.
Czy mogę używać FlashList v2 ze Starą Architekturą React Native?
Nie. FlashList v2 został zaprojektowany wyłącznie dla Nowej Architektury i nie będzie działać ze Starą Architekturą (Bridge). Jeśli nie możesz teraz migrować, zostań przy FlashList v1.x, który obsługuje obie architektury. Alternatywnie Legend List działa na obu.
Która biblioteka jest najlepsza do interfejsów czatowych?
Legend List ma najlepsze wbudowane wsparcie dla czatów dzięki propsom maintainScrollAtEnd (automatyczne scrollowanie przy nowych wiadomościach) i alignItemsAtEnd (wyrównywanie treści do dołu). FlashList v2 oferuje maintainVisibleContentPosition, które utrzymuje pozycję scrolla, ale nie ma dedykowanych propsów czatowych. Jeśli budujesz komunikator — Legend List jest moim zdaniem lepszym wyborem.
Czy dodanie klucza key do elementów FlashList pogarsza wydajność?
Tak — i to jest naprawdę ważna pułapka, na którą łatwo wpaść. Dodanie właściwości key bezpośrednio do komponentu elementu (nie przez keyExtractor) uniemożliwia FlashList recykling widoków, co eliminuje główną przewagę tej biblioteki. Zawsze używaj keyExtractor zamiast prop key na komponencie.
Czy Legend List jest gotowa do produkcji w 2026 roku?
Legend List jest aktywnie rozwijana i używana w projektach produkcyjnych, ale jest młodsza niż FlashList i FlatList. Jej oficjalne benchmarki są wciąż w fazie finalizacji. Dla nowych projektów, szczególnie tych z interfejsami czatowymi, jest solidnym wyborem. Dla krytycznych systemów z tysiącami użytkowników, FlashList v2 ma na ten moment po prostu więcej referencji produkcyjnych — i to się pewnie z czasem zmieni.