UX va tezlik — skeleton, memo, rasm optimallash

~15 daq · React Native · Animatsiya va UX

Muammodan boshlaylik

Ilova to'liq ishlaydi. Lekin foydalanuvchilar sharhlarda yozadi: "sekin", "qotib qoladi". Sizning telefoningizda (yangi flagman) hammasi silliq. Foydalanuvchilarning ko'pchiligida esa — 3-4 yillik o'rta Android.

Tezlik ikki xil bo'ladi:

  • Haqiqiy tezlik — kadrlar soni, yuklanish vaqti, xotira
  • Seziladigan tezlik — foydalanuvchi qanchalik tez his qiladi

Ikkalasi ham muhim. Ba'zan ikkinchisi — kamroq mehnat bilan katta natija.


1. Seziladigan tezlik: skeleton

Spinner — "kuting, nimadir bo'lyapti". Skeleton — kontentning kulrang "soyasi": foydalanuvchi nima kelishini ko'radi va kutish qisqaroq tuyuladi.

import Animated, { useSharedValue, useAnimatedStyle, withRepeat, withTiming } from 'react-native-reanimated';
import { useEffect } from 'react';

function Suyak({ style }: { style: StyleProp<ViewStyle> }) {
  const op = useSharedValue(0.4);
  useEffect(() => { op.value = withRepeat(withTiming(1, { duration: 800 }), -1, true); }, []);
  const s = useAnimatedStyle(() => ({ opacity: op.value }));
  return <Animated.View style={[{ backgroundColor: '#e2e8f0', borderRadius: 8 }, style, s]} />;
}

export function MahsulotKartaSkeleton() {
  return (
    <View style={{ flex: 1, padding: 12, gap: 8 }}>
      <Suyak style={{ width: '100%', aspectRatio: 1 }} />
      <Suyak style={{ width: '80%', height: 16 }} />
      <Suyak style={{ width: '40%', height: 16 }} />
    </View>
  );
}

// Ekranda
if (isPending) {
  return (
    <FlatList data={[1, 2, 3, 4, 5, 6]} numColumns={2} keyExtractor={String}
      renderItem={() => <MahsulotKartaSkeleton />} scrollEnabled={false} />
  );
}

withRepeat(..., -1, true) — cheksiz, oldinga-orqaga: "nafas olayotgan" effekt.

Boshqa seziladigan tezlik usullari

  • Optimistik yangilash (32-dars) — natijani server javobini kutmasdan ko'rsatish
  • Kesh — eski ma'lumot darhol, yangisi fonda (32, 36-darslar)
  • Oldindan yuklash — mahsulot kartasi bosilishidan oldin uning ma'lumotini so'rash:
const queryClient = useQueryClient();
<Pressable onPressIn={() => queryClient.prefetchQuery({ queryKey: ['mahsulot', id], queryFn: ... })} onPress={ochish}>

onPressIn dan onPress gacha ~100 ms — so'rov allaqachon yo'lda.

  • Darhol javob — tugma bosilganda darhol vizual o'zgarish (10-dars), so'rov fonda

2. Keraksiz qayta chizishlar

Eng ko'p uchraydigan sekinlik sababi — komponentlar keraksiz qayta chiziladi.

memo, useCallback, useMemo

// ❌ Ota qayta chizilganda — 200 ta karta ham qayta chiziladi
function Katalog() {
  const [qidiruv, setQidiruv] = useState('');
  return (
    <FlatList
      data={mahsulotlar}
      renderItem={({ item }) => <MahsulotKarta mahsulot={item} onPress={() => ochish(item.id)} />}
    />
  );
}

Har bir harf kiritilganda Katalog qayta chiziladi → yangi onPress funksiyalari → barcha kartalar qayta chiziladi.

// ✅
const MahsulotKarta = memo(function MahsulotKarta({ mahsulot, onPress }: Props) { ... });

function Katalog() {
  const ochish = useCallback((id: number) => router.push(`/mahsulot/${id}`), []);
  const renderItem = useCallback(
    ({ item }: { item: Mahsulot }) => <MahsulotKarta mahsulot={item} onPress={ochish} />,
    [ochish]
  );
  const filtrlangan = useMemo(() => mahsulotlar.filter(...), [mahsulotlar, qidiruv]);
  return <FlatList data={filtrlangan} renderItem={renderItem} />;
}
  • memo — props o'zgarmasa, komponent qayta chizilmaydi
  • useCallback — funksiya har render da yangi yaratilmasin (aks holda memo foydasiz)
  • useMemo — og'ir hisob (filtr, saralash) faqat kerak bo'lganda

[!INFO] React Compiler (yangi Expo shablonlarida yoqilishi mumkin) memo / useCallback / useMemo ni avtomatik qo'shadi. U yoqilgan bo'lsa, qo'lda yozish kamdan-kam kerak. Lekin nima uchun kerakligini bilish — compiler yordam bera olmaydigan holatlarni tushunish uchun muhim.

Zustand selektorlari

33-darsdagi qoida: useSavat() — butun store, har o'zgarishda qayta chizish. useSavat((s) => s.soni) — faqat kerakli qism.

Context ni bo'lish

Tez-tez o'zgaradigan qiymatni (masalan, joriy vaqt) katta Context ga qo'ymang — uni ishlatadigan barcha komponentlar qayta chiziladi.


3. Ro'yxatlar

19-darsdan:

  • Uzun ro'yxat — FlatList yoki FlashList, hech qachon ScrollView + map
  • renderItem — memo komponent
  • Bir xil balandlik — getItemLayout
  • keyExtractor — noyob id
  • Karta ichida og'ir hisob yoki ichki FlatList — yo'q

4. Rasmlar

Rasmlar — xotira va trafikning asosiy iste'molchisi:

  • expo-image — diskda kesh, cachePolicy (7-dars)
  • Serverdan kerakli o'lchamdagi rasm: ro'yxatda 300px, tafsilotda 1200px — 4000px emas
  • WebP / AVIF formatlar — JPEG dan 30-50% kichik
  • Yuklashdan oldin kichraytirish (40-dars)
  • placeholder (blurhash) — bo'sh kvadrat o'rniga

5. O'lchash

Taxmin qilmang — o'lchang.

  • Performance Monitor — Dev menyu → "Perf Monitor": UI va JS kadrlar soni real vaqtda. 60 dan pastga tushsa — muammo
  • React DevTools Profiler (5-dars) — qaysi komponent qancha marta va qancha vaqt chizilyapti
  • Haqiqiy past darajali qurilmada sinang — emulyator ko'pincha kompyuter protsessorida tez ishlaydi
  • Production build da o'lchang — ishlab chiqish rejimi (dev) ancha sekin: qo'shimcha tekshiruvlar va debug. Dev da "sekin" — production da ko'pincha normal:
npx expo start --no-dev --minify

Hermes

React Native standart holatda Hermes JavaScript dvigatelida ishlaydi — mobil qurilmalar uchun optimallashtirilgan: tezroq ishga tushish, kamroq xotira. Expo da yoqilgan — hech narsa qilish shart emas.


6. Ilova ishga tushish vaqti

Splash ekrandan birinchi ekrangacha — 1-2 soniyadan oshmasin:

  • _layout.tsx da og'ir sinxron ish yo'q
  • Shriftlar, tokenni tekshirish — parallel, splash fonda (30-dars)
  • Katta kutubxonalarni import qilish — faqat kerakli ekranda
  • Kerak bo'lmagan paketlarni olib tashlang — ilova hajmi va ishga tushish

UX tekshiruv ro'yxati

☐ Har bir yuklanish holati — skeleton yoki spinner
☐ Har bir xato holati — xabar va "Qayta urinish"
☐ Bo'sh holatlar — "Hali buyurtma yo'q" + keyingi qadam
☐ Har bir tugma — bosilganda vizual javob
☐ Ikki marta yuborishdan himoya
☐ Klaviatura maydonlarni yopmaydi (23-dars)
☐ Safe area — notch va pastki indikator (18-dars)
☐ Qorong'i rejim (17-dars)
☐ Katta shrift sozlamasida buzilmaydi (15-dars)
☐ Offline holatda ishlaydi yoki aniq xabar (36-dars)
☐ Past darajali Android da 60 kadr

Nima uchun shunday?

Nima uchun seziladigan tezlik haqiqiy tezlikdan muhimroq bo'lishi mumkin?

Chunki inson vaqtni obyektiv o'lchamaydi. 2 soniyalik bo'sh ekran — "uzoq va noaniq"; 2 soniyalik skeleton, keyin silliq paydo bo'lgan kontent — "tez". Tadqiqotlar ko'rsatadiki, foydalanuvchilar nimadir bo'layotganini ko'rsa, kutishni ancha osonroq qabul qiladi. Optimistik yangilash esa kutishni umuman yo'q qiladi — foydalanuvchi uchun amal darhol bajarildi. Ko'pincha bitta skeleton — bir haftalik backend optimizatsiyasidan ko'proq qoniqish beradi.


Xulosa

Eslab qoling:

  • Skeleton — spinner dan yaxshiroq; optimistik yangilash, kesh, prefetchQuery
  • memo + useCallback + useMemo — keraksiz qayta chizishlar; React Compiler buni avtomatlashtiradi
  • Zustand selektorlari; tez o'zgaradigan qiymatlar — katta Context da emas
  • Ro'yxatlar — FlatList/FlashList, memo elementlar
  • Rasmlar — expo-image, kerakli o'lcham, zamonaviy formatlar
  • O'lchang: Perf Monitor, Profiler, past darajali telefon, production rejim
  • UX ro'yxati: yuklanish, xato, bo'sh holat, feedback

Keyingi dars

48-dars: EAS Build — APK va AAB yasash, development build