UX va tezlik — skeleton, memo, rasm optimallash
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 chizilmaydiuseCallback— funksiya har render da yangi yaratilmasin (aks holdamemofoydasiz)useMemo— og'ir hisob (filtr, saralash) faqat kerak bo'lganda
[!INFO] React Compiler (yangi Expo shablonlarida yoqilishi mumkin)
memo/useCallback/useMemoni 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 —
FlatListyokiFlashList, hech qachonScrollView+map renderItem—memokomponent- Bir xil balandlik —
getItemLayout keyExtractor— noyobid- 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.tsxda 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,
memoelementlar - 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