FlatList — katta ro'yxatlar, keyExtractor, pull-to-refresh

~20 daq · React Native · Ro'yxatlar va formalar

Muammodan boshlaylik

Katalogda 2000 ta mahsulot. ScrollView + map bilan chizdingiz (8-dars). Natija:

  • Ekran 4 soniya oq turadi — 2000 ta karta va 2000 ta rasm birdaniga yaratilmoqda
  • Scroll uzilib-uzilib ketadi
  • Eski telefonlarda ilova xotira tugab, yopiladi

Ekranda bir vaqtda faqat 8-10 ta karta ko'rinadi. Qolgan 1990 tasini chizish — isrof. FlatList aynan shuni hal qiladi.


FlatList asoslari

import { FlatList, View, Text, StyleSheet } from 'react-native';

type Mahsulot = { id: number; nom: string; narx: number };

const mahsulotlar: Mahsulot[] = Array.from({ length: 2000 }, (_, i) => ({
  id: i + 1,
  nom: `Mahsulot ${i + 1}`,
  narx: (i + 1) * 15000,
}));

export default function Katalog() {
  return (
    <FlatList
      data={mahsulotlar}
      keyExtractor={(item) => String(item.id)}
      renderItem={({ item }) => (
        <View style={styles.band}>
          <Text style={styles.nom}>{item.nom}</Text>
          <Text style={styles.narx}>{item.narx.toLocaleString()} so'm</Text>
        </View>
      )}
      contentContainerStyle={{ padding: 16, gap: 8 }}
    />
  );
}

const styles = StyleSheet.create({
  band: { backgroundColor: 'white', padding: 16, borderRadius: 10 },
  nom: { fontSize: 16, fontWeight: '600' },
  narx: { color: '#0284c7', marginTop: 4 },
});
Xususiyat Ma'nosi
data Ma'lumotlar massivi
renderItem Bitta elementni chizish funksiyasi; { item, index } oladi
keyExtractor Har bir elementning noyob kaliti — string

FlatList faqat ekrandagi va uning atrofidagi elementlarni chizadi. Scroll qilganingizda — chiqib ketganlarini o'chiradi, kirayotganlarini yaratadi. 2000 ham, 200 000 ham — silliq. Bu virtualizatsiya.


renderItem ni alohida komponentga

import { memo } from 'react';

const MahsulotBand = memo(function MahsulotBand({ item }: { item: Mahsulot }) {
  return (
    <View style={styles.band}>
      <Text style={styles.nom}>{item.nom}</Text>
      <Text style={styles.narx}>{item.narx.toLocaleString()} so'm</Text>
    </View>
  );
});

<FlatList
  data={mahsulotlar}
  keyExtractor={(m) => String(m.id)}
  renderItem={({ item }) => <MahsulotBand item={item} />}
/>

memo — element props o'zgarmasa qayta chizilmaydi. Uzun ro'yxatlarda sezilarli tezlik.


Ro'yxat qismlari

<FlatList
  data={mahsulotlar}
  renderItem={...}
  ListHeaderComponent={<Text style={styles.sarlavha}>Barcha mahsulotlar</Text>}
  ListFooterComponent={yuklanmoqda ? <ActivityIndicator style={{ margin: 16 }} /> : null}
  ListEmptyComponent={<Text style={styles.bosh}>Hech narsa topilmadi</Text>}
  ItemSeparatorComponent={() => <View style={{ height: StyleSheet.hairlineWidth, backgroundColor: '#e2e8f0' }} />}
/>
  • ListHeaderComponent — ro'yxat bilan birga aylanadigan sarlavha (banner, qidiruv, filtrlar). ScrollView ichiga FlatList qo'yish o'rniga — shu (8-dars)
  • ListEmptyComponent — data bo'sh bo'lganda
  • ItemSeparatorComponent — elementlar orasida (birinchisidan oldin va oxirgisidan keyin emas)

Pastga tortib yangilash

const [yangilanmoqda, setYangilanmoqda] = useState(false);

async function yangila() {
  setYangilanmoqda(true);
  await mahsulotlarniYukla();
  setYangilanmoqda(false);
}

<FlatList
  data={mahsulotlar}
  renderItem={...}
  refreshing={yangilanmoqda}
  onRefresh={yangila}
/>

Ikki xususiyat — va har bir mobil foydalanuvchiga tanish "pull-to-refresh" tayyor.


Ustunlar (grid)

<FlatList
  data={mahsulotlar}
  numColumns={2}
  key="grid-2"                                   // numColumns o'zgarsa — key ham
  columnWrapperStyle={{ gap: 12 }}
  contentContainerStyle={{ padding: 16, gap: 12 }}
  renderItem={({ item }) => (
    <View style={{ flex: 1, backgroundColor: 'white', borderRadius: 12, padding: 12 }}>
      <Image source={{ uri: item.rasm }} style={{ width: '100%', aspectRatio: 1, borderRadius: 8 }} />
      <Text numberOfLines={2}>{item.nom}</Text>
    </View>
  )}
/>
  • flex: 1 — har bir karta ustun kengligini teng egallaydi
  • columnWrapperStyle — har bir qator uchun stil (ustunlar orasidagi gap)
  • Toq sonli elementlarda oxirgi karta butun qatorni egallab qoladi — buni oldini olish uchun maxWidth: '48%' yoki bo'sh "soxta" element

Gorizontal ro'yxat

<FlatList
  horizontal
  data={toifalar}
  keyExtractor={(t) => t.id}
  renderItem={({ item }) => <ToifaChip toifa={item} />}
  showsHorizontalScrollIndicator={false}
  contentContainerStyle={{ paddingHorizontal: 16, gap: 8 }}
/>

Bosh sahifadagi "Ommabop", "Yangi kelganlar" qatorlari. Vertikal FlatList ning ListHeaderComponent i ichida gorizontal FlatList — muammo emas (yo'nalishlar har xil).


Tezlik sozlamalari

<FlatList
  data={...}
  renderItem={...}
  initialNumToRender={10}       // birinchi chizishda nechta
  maxToRenderPerBatch={10}
  windowSize={7}                // ekrandan nechta "ekran" oldin/keyin xotirada
  removeClippedSubviews         // Android: ekrandan tashqarisini ajratish
  getItemLayout={(_, index) => ({ length: 80, offset: 80 * index, index })}
/>

getItemLayout — barcha elementlar bir xil balandlikda bo'lsa (masalan, 80). FlatList har birini o'lchamasdan pozitsiyani biladi — scrollToIndex va tez scroll ancha silliq.

FlashList — yanada tez

Juda uzun va murakkab ro'yxatlar uchun Shopify ning FlashList kutubxonasi:

npx expo install @shopify/flash-list
import { FlashList } from '@shopify/flash-list';

<FlashList data={mahsulotlar} renderItem={...} />

FlatList bilan deyarli bir xil API, lekin elementlarni yo'q qilish o'rniga qayta ishlatadi (recycling) — ko'p hollarda sezilarli tezroq.


Kodda scroll

const ref = useRef<FlatList<Mahsulot>>(null);

ref.current?.scrollToOffset({ offset: 0, animated: true });   // tepaga
ref.current?.scrollToIndex({ index: 50, animated: true });     // 50-elementga

Tab ikonkasi ikkinchi marta bosilganda ro'yxat tepasiga qaytish — klassik UX.


Nima uchun shunday?

Nima uchun keyExtractor majburiy va u nima uchun string bo'lishi kerak?

FlatList elementlarni yaratib-o'chirib turadi. Kalit — React ga "bu element o'shami?" degan savolga javob beradi. Kalit sifatida indeks ishlatilsa va ro'yxat boshiga yangi element qo'shilsa — barcha indekslar siljiydi, React esa har bir elementni "o'zgargan" deb qayta chizadi (yoki yomonroq — noto'g'ri elementning holatini saqlab qoladi: belgilangan checkbox boshqa qatorga "sakraydi"). Noyob id asosidagi kalit bu muammolarni yo'q qiladi. String bo'lishi — React Native ning ichki talabi.


Xulosa

Eslab qoling:

  • FlatList — virtualizatsiya: faqat ko'rinadiganlar chiziladi
  • data, renderItem, keyExtractor (noyob id, string) — majburiy uchlik
  • renderItem — alohida memo komponent
  • ListHeaderComponent, ListFooterComponent, ListEmptyComponent, ItemSeparatorComponent
  • refreshing + onRefresh — pull-to-refresh
  • numColumns — grid; horizontal — gorizontal qator
  • Bir xil balandlik — getItemLayout; juda katta ro'yxatlar — FlashList

Keyingi dars

20-dars: SectionList va cheksiz scroll (onEndReached)