15 β Tabs va Drawer navigatsiya¶
β¬ οΈ Oldingi: 14 β Expo Router asoslari Β· π Kitob boshi Β· Keyingi: 16 β Dinamik marshrut va parametrlar β‘οΈ
Bu bobda: mobil ilovalarning eng tanish ikki navigatsiya naqshini o'rganamiz β pastdagi tab menyu (Bosh / Qidiruv / Profil) va chetdan ochiladigan drawer (yon menyu). Route guruhlari
(tabs)qanday ishlashini, tab ikonkalarini@expo/vector-iconsbilan qo'yishni, nested (ichma-ich) navigatorlarni β Stack ichida Tabs, tab ichida yana Stack β va modal ekranni ko'ramiz. Oxirida 3 tabli to'liq ilovani noldan yig'amiz.
Kirish: nega tab va drawer kerak?¶
O'tgan bobda Expo Router bilan tanishdik: fayl = marshrut, app/_layout.tsx da Stack navigatori ekranlarni uyum (stack) qilib ustma-ust qo'yardi. Stack β bu "orqaga" tugmasi bilan ishlaydigan navigatsiya: ekranga kirasiz, ustiga yangisi tushadi, orqaga qaytasiz. Bu mukammal naqsh, lekin u butun ilovaga yetmaydi.
Telefoningizdagi istalgan jiddiy ilovani oching β Instagram, Telegram, YouTube, Spotify. Eng pastida nima turibdi? Bir qator tugma: bosh sahifa, qidiruv, profil... Bularning birortasini bossangiz, butun ekran almashadi β lekin siz hech qayerga "chuqurlashmaysiz", shunchaki bo'limlar orasida sakraysiz. Aynan shu β tab navigatsiya. Bu mobil ilovalarning eng keng tarqalgan naqshi.
Hayotiy o'xshatish. Tab menyu β bu televizor pulti tugmalari kabi. Pultda "1-kanal", "2-kanal", "3-kanal" tugmalari bor. Qaysi birini bossangiz, butun ekran o'sha kanalga o'tadi. Lekin kanallar bir-birining ustiga "tushmaydi" β ular yonma-yon, teng huquqli. Tab tugmalarini bossangiz ham xuddi shunday: ekran butunlay almashadi, lekin "orqaga" qaytish kerak emas β istalgan tugmani bosib, istalgan bo'limga o'tasiz.
Stack va Tabs bir-birini inkor etmaydi β aksincha, ular birga ishlaydi. Bir tab ichida elementga bosib, uning batafsil sahifasiga (detal) o'tish kerak bo'lsa, o'sha tabning ichiga Stack joylashtiramiz. Bu β nested navigator g'oyasi, va bu bobning eng qimmatli tushunchasi.
Keling, eng oddiy tab menyudan boshlaymiz.
1. Tab navigatsiya: birinchi misol¶
O'tgan bobda Stack uchun app/_layout.tsx ga <Stack> qo'ygan edik. Tabs uchun ham xuddi shu β faqat <Stack> o'rniga <Tabs> ishlatamiz.
Hayotiy o'xshatish. Layout fayli β bu rom (ramka) kabi. Devorga rasm osmoqchisiz: rasmlar (ekranlar) almashishi mumkin, lekin rom doimo turadi.
Stackromi rasmlarni ketma-ket ko'rsatadi,Tabsromi esa pastida tugmalar qatorini chizib, har tugmaga bitta rasm bog'laydi.
Eng sodda tab menyu:
// app/(tabs)/_layout.tsx
import { Tabs } from 'expo-router/js-tabs';
export default function TabsLayout() {
return (
<Tabs>
<Tabs.Screen name="index" options={{ title: 'Bosh' }} />
<Tabs.Screen name="profil" options={{ title: 'Profil' }} />
</Tabs>
);
}
Bu yerda nimalar bo'lyapti:
<Tabs>β navigator komponenti. U avtomatik ravishda pastki menyu chizadi.<Tabs.Screen name="index" />β ekranni navigatorga ro'yxatdan o'tkazadi.nameβ fayl nomi (kengaytmasiz):index->index.tsx,profil->profil.tsx.options={{ title: 'Bosh' }}β shu tabning tagidagi yozuv va yuqoridagi sarlavha.
Endi shu ikki ekranning o'zini yaratamiz:
// app/(tabs)/index.tsx
import { View, Text, StyleSheet } from 'react-native';
export default function BoshEkran() {
return (
<View style={styles.box}>
<Text style={styles.matn}>Bosh sahifa</Text>
</View>
);
}
const styles = StyleSheet.create({
box: { flex: 1, alignItems: 'center', justifyContent: 'center' },
matn: { fontSize: 22, fontWeight: '700', color: '#1e293b' },
});
// app/(tabs)/profil.tsx
import { View, Text, StyleSheet } from 'react-native';
export default function ProfilEkran() {
return (
<View style={styles.box}>
<Text style={styles.matn}>Profil sahifasi</Text>
</View>
);
}
const styles = StyleSheet.create({
box: { flex: 1, alignItems: 'center', justifyContent: 'center' },
matn: { fontSize: 22, fontWeight: '700', color: '#1e293b' },
});
Tayyor! Ilovani ishga tushiring (npx expo start), Expo Go'da oching β pastida ikki tugma paydo bo'ladi: "Bosh" va "Profil". Bittasini bosing β ekran almashadi. Bu hammasi β biz birorta murakkab kod yozmadik.
Import yo'li β SDK 56 yangiligi
Expo SDK 56'da Tabsni expo-router/js-tabs dan import qilamiz. Ilgari (eski boblar va eski qo'llanmalar) import { Tabs } from 'expo-router' deb yozardi β bu hali ham ishlaydi, lekin SDK 56 uni eskirgan (deprecated) deb belgilab, ogohlantirish chiqaradi. Yangi loyihada to'g'ri yo'l β 'expo-router/js-tabs'. (Stack, Link, useRouter kabilar esa o'zgarmagan β ular 'expo-router' dan keladi.)
app/ yoki src/app/?
SDK 56 shabloni routing ildizini src/app/ qiladi. Bu bobda qisqalik uchun app/ yo'lini yozamiz, lekin agar loyihangizda src/app/ bo'lsa, qoidalar aynan bir xil β shunchaki src/ prefiksini qo'shing.
2. Route guruhlari (tabs) β eng muhim tushuncha¶
E'tibor berdingizmi: fayllarni app/(tabs)/ ichiga, qavs bilan joyladik. Bu route guruhi deb ataladi va Expo Router'ning eng nozik, lekin eng foydali xususiyatlaridan biri.
Qoida: papka nomi qavs ichida bo'lsa β (tabs), (auth), (app) β bu papka URL manziliga qo'shilmaydi. U faqat fayllarni tashkil etish va ularga umumiy layout berish uchun ishlatiladi.
Hayotiy o'xshatish. Route guruhi β bu idishlarni javondagi qutilarga ajratish kabi. Oshxonangizda piyolalar "Mehmon" qutisida, kosalar "Kundalik" qutisida turadi. Lekin dasturxonga qo'yganda hech kim "Mehmon qutisidagi piyola" demaydi β shunchaki "piyola" deydi. Quti tartib uchun, atash uchun emas.
(tabs)ham xuddi shu: kodingizni tartibga soladi, lekin foydalanuvchi ko'radigan manzilga ta'sir qilmaydi.
Mana natija:
app/(tabs)/index.tsx -> / (tabs ko'rinmaydi!)
app/(tabs)/qidiruv.tsx -> /qidiruv
app/(tabs)/profil.tsx -> /profil
index.tsx manzili /(tabs)/index emas, balki shunchaki /. Qavs ichidagi qism "ko'rinmas". Bu juda muhim, chunki:
- URL toza qoladi. Foydalanuvchi
/profilko'radi,/(tabs)/profilemas. - Bir nechta guruh bir xil darajada bo'lishi mumkin. Masalan,
(auth)guruhida login/ro'yxatdan o'tish ekranlari (tab menyusiz),(tabs)guruhida asosiy ilova (tab menyu bilan). Foydalanuvchi kirgan-kirmaganiga qarab birini yoki ikkinchisini ko'rsatasiz β lekin manzillar ikkalasida ham bir xil ildizdan boshlanadi.
Eng ko'p uchraydigan adashish
Boshlovchilar <Link href="/(tabs)/profil"> deb yozishga harakat qiladi. Buning hojati yo'q β manzil shunchaki /profil. Qavsli qismni manzilga qo'shmang. Faqat <Tabs.Screen name="..." /> da fayl nomini yozasiz, havolada esa toza URL ishlatasiz.
Tekshirib ko'ring
app/(tabs)/profil.tsx faylini yarating va <Tabs.Screen name="profil" /> qo'shing. So'ng index.tsx ga <Link href="/profil"><Text>Profilga</Text></Link> joylang. Havola ishlashini ko'ring. Endi href="/(tabs)/profil" deb o'zgartiring β TypeScript ogohlantirishini (typed routes) yoki noto'g'ri yo'nalishni ko'rasiz. Toza manzilni eslang.
3. Tab ikonkalari β @expo/vector-icons¶
Yozuvsiz ikonka β bu tor menyu, ikonkasiz yozuv β quruq. Yaxshi tab menyu ikonka + yozuv beradi. RN'da ikonkalar uchun rasmiy yechim β @expo/vector-icons paketi.
Bu paket ichida juda ko'p ikonka to'plami bor (Ionicons, MaterialIcons, FontAwesome, Feather...). Boshlovchi uchun eng qulayi β Ionicons (iOS uslubidagi, lekin har ikki platformada chiroyli).
Avval paketni o'rnatamiz:
Eslatma
Ko'pincha bu paket Expo loyihasiga avtomatik kiradi (default shablon uni ishlatadi). Lekin toza loyihada bo'lmasligi mumkin β npx expo install bilan qo'shing. (npx expo install ishlatish muhim: u SDK'ga mos versiyani tanlaydi, oddiy npm install esa har doim ham emas.)
Endi har Tabs.Screen ga tabBarIcon beramiz. Bu β funksiya: u color (rang) va size (o'lcham)ni oladi va JSX qaytaradi. Rang/o'lchamni navigator beradi β biz uni ikonkaga uzatamiz, shunda faol/nofaol holatda ikonka avtomatik ranglanadi:
// app/(tabs)/_layout.tsx
import { Tabs } from 'expo-router/js-tabs';
import { Ionicons } from '@expo/vector-icons';
export default function TabsLayout() {
return (
<Tabs
screenOptions={{
tabBarActiveTintColor: '#4f46e5', // faol tab rangi
tabBarInactiveTintColor: '#94a3b8', // nofaol tab rangi
}}
>
<Tabs.Screen
name="index"
options={{
title: 'Bosh',
tabBarIcon: ({ color, size }) => (
<Ionicons name="home" size={size} color={color} />
),
}}
/>
<Tabs.Screen
name="qidiruv"
options={{
title: 'Qidiruv',
tabBarIcon: ({ color, size }) => (
<Ionicons name="search" size={size} color={color} />
),
}}
/>
<Tabs.Screen
name="profil"
options={{
title: 'Profil',
tabBarIcon: ({ color, size }) => (
<Ionicons name="person" size={size} color={color} />
),
}}
/>
</Tabs>
);
}
Endi har tab o'z ikonkasiga ega. Faol tab indigo (#4f46e5), qolganlari kulrang (#94a3b8). E'tibor bering β biz rangni o'zimiz yozmaymiz ikonkaga; navigatordan kelgan colorni o'tkazamiz, shunda u faol/nofaol holatga qarab o'zgaradi. Bu juda muhim naqsh.
Ikonka nomini qanday topish
Ionicons ichida minglab ikonka bor: home, search, person, settings, heart, cart, notifications, add-circle... To'liq ro'yxatni icons.expo.fyi saytida ko'rish mumkin (qidiruv bilan). To'plam variantlari ham bor: home (to'la), home-outline (faqat chiziq). Nofaol uchun -outline, faol uchun to'la ko'rsatish β chiroyli naqsh.
3.1 Qo'shimcha tab sozlamalari¶
Tabs.Screen options'ida yana ko'p foydali sozlamalar bor:
tabBarLabelβ tab tagidagi yozuv (agartitledan farqli bo'lishini xohlasangiz). Sarlavha "Profil", lekin tabda "Mening profilim" yozilsin desangiz.tabBarBadgeβ ikonka ustidagi qizil belgicha (raqam yoki matn). Yangi xabarlar sonini ko'rsatish uchun ideal.tabBarActiveTintColor/tabBarInactiveTintColorβ yuqorida ko'rdik, faol/nofaol rang.headerShown: falseβ yuqoridagi sarlavhani yashirish.
Misol β profil tabiga "3" badge va alohida yozuv:
<Tabs.Screen
name="profil"
options={{
title: 'Profil',
tabBarLabel: 'Mening profilim', // tab tagidagi yozuv
tabBarBadge: 3, // ikonka ustida qizil "3"
tabBarIcon: ({ color, size }) => (
<Ionicons name="person" size={size} color={color} />
),
}}
/>
Badge'ni dinamik qilish
Haqiqiy ilovada tabBarBadge qiymati holatdan keladi β masalan o'qilmagan xabarlar soni. Buni global holat (19-bob) yoki context bilan boshqarasiz. Badge undefined bo'lsa β ko'rinmaydi; raqam bo'lsa β qizil doirada chiqadi.
4. Drawer (yon menyu)¶
Tab menyu β pastdagi 3-5 ta bo'lim uchun ajoyib. Lekin bo'limlar ko'p bo'lsa (8-10 ta), yoki ular asosiy emas, balki "ikkinchi darajali" bo'lsa (sozlamalar, yordam, chiqish), pastga sig'maydi. Bunda drawer β chetdan surib ochiladigan yon menyu β qo'l keladi.
Hayotiy o'xshatish. Drawer β bu shkaf eshigi kabi. U doimo ko'rinmaydi β devorga qo'shilib turadi. Lekin chetidan tortsangiz (yoki yuqoridagi "uch chiziq" tugmasini bossangiz), eshik ochiladi va ichidagi javonlar (menyu bo'limlari) ko'rinadi. Kerakli narsani oldingizdan keyin eshik yana yopiladi. Gmail, ko'plab Google ilovalari aynan shu naqshni ishlatadi.
Drawer ikkita native kutubxonaga tayanadi:
react-native-gesture-handlerβ barmoq harakatlarini (chetdan surish) tutadi.react-native-reanimatedβ menyuning silliq ochilish/yopilish animatsiyasi.
Yaxshi xabar β SDK 56'da osonroq
Bu kutubxonalar Expo SDK 56 default shablonida allaqachon o'rnatilgan (chunki ular boshqa joylarda ham kerak). Drawer komponentining o'zi expo-router/drawer dan keladi va alohida @react-navigation/drawer paketini qo'lda o'rnatish shart emas β u Expo Router ichiga jamlangan. Agar toza loyiha bo'lsa, ishonch uchun: npx expo install react-native-gesture-handler react-native-reanimated.
Drawer'ni odatda ildiz app/_layout.tsx ga qo'yamiz:
// app/_layout.tsx
import Drawer from 'expo-router/drawer';
import { Ionicons } from '@expo/vector-icons';
export default function RootLayout() {
return (
<Drawer screenOptions={{ drawerActiveTintColor: '#4f46e5' }}>
<Drawer.Screen
name="index"
options={{
title: 'Bosh',
drawerLabel: 'Bosh sahifa',
drawerIcon: ({ color, size }) => (
<Ionicons name="home" size={size} color={color} />
),
}}
/>
<Drawer.Screen
name="sozlamalar"
options={{
title: 'Sozlamalar',
drawerIcon: ({ color, size }) => (
<Ionicons name="settings" size={size} color={color} />
),
}}
/>
</Drawer>
);
}
E'tibor bering β tuzilish Tabs bilan deyarli bir xil: <Drawer> o'rab turadi, ichida <Drawer.Screen> har bir ekran uchun. Faqat prefiks tabBar... o'rniga drawer... (drawerLabel, drawerIcon, drawerActiveTintColor). Bir navigatorni o'rgansangiz, qolganlari osongina o'rganiladi.
Drawer ekranlari ham oddiy fayl: app/index.tsx, app/sozlamalar.tsx. Ochish uchun foydalanuvchi chetdan suradi yoki yuqoridagi avtomatik chiqadigan "uch chiziq" tugmasini bosadi.
4.1 Qachon Tabs, qachon Drawer?¶
Ikkalasi ham "yuqori darajadagi" bo'limlarni ko'rsatadi. Tanlash mezoni:
| Tabs (pastki menyu) | Drawer (yon menyu) |
|---|---|
| 3-5 ta asosiy bo'lim | Ko'p (6+) yoki ikkinchi darajali bo'limlar |
| Doimo ko'rinadi β bir bosishda o'tasiz | Yashirin β ochish kerak |
| Instagram, Telegram, YouTube | Gmail, ko'p admin-panellar |
| Eng tez-tez ishlatiladigan bo'limlar | Sozlamalar, profil, yordam, chiqish |
Ikkalasini birga ishlatish
Ko'plab katta ilovalar ikkalasini ham ishlatadi: asosiy 4-5 bo'lim pastda tab, qolgan "qo'shimcha" bo'limlar drawer'da. Buni nested navigator bilan quramiz β drawer ildizda, uning ichida bitta ekran o'rnida butun tab navigatori turadi. Lekin boshlovchi uchun bittasidan boshlash tavsiya etiladi.
5. Nested navigatorlar β navigator ichida navigator¶
Endi bobning eng kuchli g'oyasiga keldik. Haqiqiy ilovalar bitta navigatordan iborat emas β ular bir nechta navigatorni ichma-ich joylaydi.
Hayotiy o'xshatish. Nested navigatorlar β bu bino, qavatlar va xonalar kabi. Bino (ildiz Stack) β eng tashqi qobiq. Ichida liftga chiqasiz, har qavatga (tab) o'tasiz. Har qavatda esa o'z xonalari (ichki Stack ekranlari) bor β bir xonadan ikkinchisiga o'tasiz. Tashqi qobiq ichidagilarni ushlab turadi; har qavat o'z ichida mustaqil harakatlanadi.
Eng tipik tuzilish:
Ildiz Stack (app/_layout.tsx)
ββ (tabs) <- Tabs navigatori (app/(tabs)/_layout.tsx)
β ββ Bosh <- ichki Stack (tabda push qilish uchun)
β ββ Qidiruv <- oddiy ekran
β ββ Profil <- oddiy ekran
ββ modal <- modal ekran (tabdan tashqarida, ustiga chiqadi)
Diagrammadan ko'rinib turibdiki, ildiz Stack eng tepada β u butun ilovani o'raydi. Uning ichida (tabs) guruhi turadi, va u bitta ekran sifatida ko'rinadi Stack uchun β lekin aslida butun tab navigatori. Va modal β tablardan tashqarida, ildiz Stack darajasida, shu sababli u tab menyu ustiga chiqadi (tablar ko'rinmay qoladi).
5.1 Nega ildizda Stack kerak?¶
Faqat tab menyu yetarli bo'lardi, agar har tab "tekis" ekran bo'lsa. Lekin biz ko'pincha:
- Modal ekranlar qo'shamiz (yangi yozuv yaratish, rasm ochish) β ular tab tashqarisida turishi kerak.
- Login/auth ekranlarini tab menyusiz ko'rsatamiz.
Shu sababli eng yaxshi amaliyot β ildizda Stack, uning bitta "ekrani" o'rnida (tabs):
// app/_layout.tsx β ildiz Stack
import { Stack } from 'expo-router';
export default function RootLayout() {
return (
<Stack>
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
<Stack.Screen
name="modal"
options={{ presentation: 'modal', title: 'Yangi yozuv' }}
/>
</Stack>
);
}
E'tibor bering:
<Stack.Screen name="(tabs)" />β bu yerda(tabs)butun tab navigatorga ishora qiladi (app/(tabs)/_layout.tsx).headerShown: falseβ chunki tab navigatorning o'z sarlavhasi bor, ikkalasi chiqmasin.<Stack.Screen name="modal" />βpresentation: 'modal'bilan. Bu ekran pastdan surib chiqadigan modal sifatida ochiladi.
5.2 Modal ekran¶
Modal β bu asosiy oqimni "to'xtatib", ustiga chiqadigan ekran: yangi yozuv yozish, sozlama tanlash, tasdiqlash. iOS'da u pastdan ko'tariladi va tepada "tortib yopish" imkoni beradi.
Modal ekranning o'zi β oddiy fayl:
// app/modal.tsx
import { View, Text, StyleSheet, Pressable } from 'react-native';
import { useRouter } from 'expo-router';
export default function ModalEkran() {
const router = useRouter();
return (
<View style={styles.box}>
<Text style={styles.sarlavha}>Yangi yozuv</Text>
<Text style={styles.matn}>Bu modal ekran β asosiy oqim ustida chiqadi.</Text>
<Pressable style={styles.tugma} onPress={() => router.back()}>
<Text style={styles.tugmaMatn}>Yopish</Text>
</Pressable>
</View>
);
}
const styles = StyleSheet.create({
box: { flex: 1, alignItems: 'center', justifyContent: 'center', gap: 14, padding: 24 },
sarlavha: { fontSize: 22, fontWeight: '800', color: '#1e293b' },
matn: { fontSize: 15, color: '#475569', textAlign: 'center' },
tugma: { backgroundColor: '#4f46e5', paddingVertical: 12, paddingHorizontal: 28, borderRadius: 10 },
tugmaMatn: { color: '#fff', fontWeight: '600', fontSize: 15 },
});
Modalni ochish β oddiy navigatsiya: router.push('/modal') yoki <Link href="/modal">. Yopish uchun router.back(). Chunki u Stack'da presentation: 'modal' bilan ro'yxatga olingan, Expo Router uni avtomatik modal sifatida chiqaradi.
iOS va Android farqi
presentation: 'modal' iOS'da yaqqol "kartochka" effekti beradi β ekran pastdan ko'tariladi, orqada eski ekran ko'rinib turadi, pastga tortib yopiladi. Android'da effekt ozroq boshqacha (to'liq ekran sirpanish), lekin mantiq bir xil. Kross-platforma ilovada bu farqdan tashvishlanmang β har platforma o'z odatiga mos ko'rsatadi.
5.3 Tab ichida detalga push¶
Eng ko'p uchraydigan ehtiyoj: tabda ro'yxat bor (masalan postlar), elementni bosib detal sahifasiga o'tish kerak. Detal β yangi ekran, "orqaga" tugmasi bilan. Bu β Stack vazifasi. Demak, shu tabning ichiga Stack joylaymiz.
Buning uchun tabning fayli o'rniga papka qilamiz, ichiga _layout.tsx (Stack) qo'yamiz:
app/(tabs)/
ββ _layout.tsx <- Tabs navigatori
ββ index.tsx <- (agar Bosh tab papka bo'lmasa)
ββ qidiruv.tsx
ββ profil/ <- Profil tab β endi PAPKA
ββ _layout.tsx <- ichki Stack
ββ index.tsx <- profil bosh ekrani
ββ [id].tsx <- detal ekrani
Bu yerda diqqat: tabning fayli profil.tsx emas, balki profil/ papka bo'ldi, va uning ichida _layout.tsx (Stack) bor. Tab tugmasi profil papkaga ishora qiladi, ichidagi index esa boshlang'ich ekran.
Soddaroq va keng tarqalgan namuna β Bosh tabda ro'yxat, undan detalga o'tish. Detal sahifasini esa biz app/(tabs)/ darajasida emas, ko'pincha alohida marshrutda saqlaymiz, masalan app/maqola/[id].tsx (ildiz Stack ichida). Shunda detal ochilganda tab menyu ham ko'rinib turadi yoki yo'qoladi β bu tuzilishga bog'liq. Eng oddiy yo'l β push'ni ildiz Stack orqali qilish:
// app/(tabs)/index.tsx β Bosh tab, ro'yxatdan detalga push
import { View, Text, StyleSheet, Pressable, FlatList } from 'react-native';
import { useRouter } from 'expo-router';
const POSTLAR = [
{ id: '1', sarlavha: 'React Native bilan tanishuv' },
{ id: '2', sarlavha: 'Expo Router asoslari' },
{ id: '3', sarlavha: 'Tab va Drawer navigatsiya' },
];
export default function BoshEkran() {
const router = useRouter();
return (
<View style={styles.box}>
<Text style={styles.sarlavha}>Postlar</Text>
<FlatList
data={POSTLAR}
keyExtractor={(item) => item.id}
renderItem={({ item }) => (
<Pressable
style={styles.qator}
onPress={() => router.push(`/maqola/${item.id}`)}
>
<Text style={styles.qatorMatn}>{item.sarlavha}</Text>
</Pressable>
)}
/>
</View>
);
}
const styles = StyleSheet.create({
box: { flex: 1, padding: 16, gap: 8 },
sarlavha: { fontSize: 22, fontWeight: '800', color: '#1e293b', marginBottom: 8 },
qator: { backgroundColor: '#fff', padding: 16, borderRadius: 10, borderWidth: 1, borderColor: '#bae6fd' },
qatorMatn: { fontSize: 15, color: '#475569' },
});
Bu yerda router.push(\/maqola/${item.id}`)β bosilgan postningidsi bilan detal sahifasiga o'tamiz. Detal sahifasi β keyingi bobning (16-bob) mavzusi: **dinamik marshrut**[id].tsx. Hozircha shuni biling:push` yangi ekranni stack ustiga qo'yadi, va foydalanuvchi "orqaga" bilan ro'yxatga qaytadi.
Tabda holat saqlanadi
Tab navigatorning ajoyib jihati: bir tabdan boshqasiga o'tib, qaytib kelsangiz β birinchi tab o'z holatida turadi (skroll joyi, kiritilgan matn, ochilgan detal). Tablar "o'chmaydi", balki orqada turaveradi. Bu foydalanuvchi uchun juda qulay β Instagram'da profildan bosh sahifaga qaytsangiz, lentaning o'sha joyida turasiz.
6. To'liq misol β 3 tabli ilova¶
Endi bilganlarimizni bitta ishlaydigan ilovaga yig'amiz: Bosh / Qidiruv / Profil uch tabli, ikonkalari bilan, va Bosh tabda detalga push qiladigan ilova. Bu kodning hammasi npx tsc --noEmit bilan tekshirilgan β ishlaydi.
Fayl tuzilishi:
app/
ββ _layout.tsx <- ildiz Stack (modal bilan)
ββ modal.tsx <- modal ekran
ββ maqola/
β ββ [id].tsx <- detal (16-bobda to'liq)
ββ (tabs)/
ββ _layout.tsx <- Tabs navigatori (ikonkalar)
ββ index.tsx <- Bosh (ro'yxat -> push)
ββ qidiruv.tsx <- Qidiruv
ββ profil.tsx <- Profil
1) Ildiz Stack:
// app/_layout.tsx
import { Stack } from 'expo-router';
export default function RootLayout() {
return (
<Stack>
{/* Tab navigatori β bitta "ekran" sifatida, o'z sarlavhasi bor */}
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
{/* Modal β tablardan tashqarida, ustiga chiqadi */}
<Stack.Screen name="modal" options={{ presentation: 'modal', title: 'Yangi yozuv' }} />
{/* Detal β push bilan ochiladi (16-bob) */}
<Stack.Screen name="maqola/[id]" options={{ title: 'Maqola' }} />
</Stack>
);
}
2) Tab navigatori (ikonkalar bilan):
// app/(tabs)/_layout.tsx
import { Tabs } from 'expo-router/js-tabs';
import { Ionicons } from '@expo/vector-icons';
export default function TabsLayout() {
return (
<Tabs
screenOptions={{
tabBarActiveTintColor: '#4f46e5',
tabBarInactiveTintColor: '#94a3b8',
}}
>
<Tabs.Screen
name="index"
options={{
title: 'Bosh',
tabBarIcon: ({ color, size }) => (
<Ionicons name="home-outline" size={size} color={color} />
),
}}
/>
<Tabs.Screen
name="qidiruv"
options={{
title: 'Qidiruv',
tabBarIcon: ({ color, size }) => (
<Ionicons name="search-outline" size={size} color={color} />
),
}}
/>
<Tabs.Screen
name="profil"
options={{
title: 'Profil',
tabBarBadge: 2,
tabBarIcon: ({ color, size }) => (
<Ionicons name="person-outline" size={size} color={color} />
),
}}
/>
</Tabs>
);
}
3) Bosh tab β ro'yxat va detalga push:
// app/(tabs)/index.tsx
import { View, Text, StyleSheet, Pressable, FlatList } from 'react-native';
import { useRouter } from 'expo-router';
const POSTLAR = [
{ id: '1', sarlavha: 'React Native bilan tanishuv' },
{ id: '2', sarlavha: 'Expo Router asoslari' },
{ id: '3', sarlavha: 'Tab va Drawer navigatsiya' },
];
export default function BoshEkran() {
const router = useRouter();
return (
<View style={styles.box}>
<Text style={styles.sarlavha}>Postlar</Text>
<FlatList
data={POSTLAR}
keyExtractor={(item) => item.id}
renderItem={({ item }) => (
<Pressable style={styles.qator} onPress={() => router.push(`/maqola/${item.id}`)}>
<Text style={styles.qatorMatn}>{item.sarlavha}</Text>
<Ionicons name="chevron-forward" size={18} color="#94a3b8" />
</Pressable>
)}
/>
<Pressable style={styles.fab} onPress={() => router.push('/modal')}>
<Ionicons name="add" size={26} color="#fff" />
</Pressable>
</View>
);
}
import { Ionicons } from '@expo/vector-icons';
const styles = StyleSheet.create({
box: { flex: 1, padding: 16, gap: 8 },
sarlavha: { fontSize: 22, fontWeight: '800', color: '#1e293b', marginBottom: 8 },
qator: {
flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between',
backgroundColor: '#fff', padding: 16, borderRadius: 10, borderWidth: 1, borderColor: '#bae6fd', marginBottom: 8,
},
qatorMatn: { fontSize: 15, color: '#475569' },
fab: {
position: 'absolute', right: 20, bottom: 20,
width: 56, height: 56, borderRadius: 28, backgroundColor: '#4f46e5',
alignItems: 'center', justifyContent: 'center',
},
});
Import joyi
Yuqorida import ni stillar yonida ko'rsatdim β bu faqat tushuntirish uchun. Haqiqiy faylda barcha importlar fayl boshida turishi kerak. import { Ionicons } from '@expo/vector-icons'; ni fayl tepasiga, qolgan importlar bilan birga qo'ying.
4) Qidiruv va Profil ekranlari (oddiy):
// app/(tabs)/qidiruv.tsx
import { View, Text, TextInput, StyleSheet } from 'react-native';
import { useState } from 'react';
export default function QidiruvEkran() {
const [matn, setMatn] = useState('');
return (
<View style={styles.box}>
<TextInput
style={styles.input}
placeholder="Qidirish..."
value={matn}
onChangeText={setMatn}
/>
<Text style={styles.natija}>So'rov: {matn || '(bo\'sh)'}</Text>
</View>
);
}
const styles = StyleSheet.create({
box: { flex: 1, padding: 16, gap: 12 },
input: { borderWidth: 1, borderColor: '#cbd5e1', borderRadius: 10, padding: 12, fontSize: 15 },
natija: { fontSize: 15, color: '#475569' },
});
// app/(tabs)/profil.tsx
import { View, Text, StyleSheet } from 'react-native';
export default function ProfilEkran() {
return (
<View style={styles.box}>
<View style={styles.avatar}>
<Text style={styles.avatarMatn}>O</Text>
</View>
<Text style={styles.ism}>Oqil Imomnazarov</Text>
<Text style={styles.kasb}>React Native dasturchisi</Text>
</View>
);
}
const styles = StyleSheet.create({
box: { flex: 1, alignItems: 'center', justifyContent: 'center', gap: 8 },
avatar: { width: 80, height: 80, borderRadius: 40, backgroundColor: '#4f46e5', alignItems: 'center', justifyContent: 'center' },
avatarMatn: { color: '#fff', fontSize: 32, fontWeight: '800' },
ism: { fontSize: 20, fontWeight: '700', color: '#1e293b' },
kasb: { fontSize: 14, color: '#475569' },
});
Mana β to'liq, ishlaydigan 3 tabli ilova: ikonkalar bilan tab menyu, badge, Bosh tabda ro'yxat va detalga push, modal ochuvchi suzuvchi tugma (FAB), qidiruv inputi va profil. Buni npx expo start bilan ishga tushiring va Expo Go'da sinab ko'ring.
Nima qildik β qisqacha
- Ildiz Stack butun ilovani o'radi, modal va detalni boshqaradi.
(tabs)guruhi URL'ga qo'shilmaydi, faqat tab layout beradi.- 3 tab har biri ikonka bilan, faol/nofaol rang avtomatik.
tabBarBadgeprofilga "2" belgicha qo'ydi.router.pushbilan detalga (/maqola/:id) va modalga (/modal) o'tdik.
7. Tez-tez uchraydigan xatolar¶
Eng ko'p uchraydigan xatolar
<Tabs.Screen name="index.tsx" />β.tsxkengaytmasi YOZILMAYDI. Faqatname="index".- Eski import β
import { Tabs } from 'expo-router'. SDK 56'da'expo-router/js-tabs'ishlating. href="/(tabs)/profil"β qavsli qism manzilga qo'shilmaydi. To'g'risi:/profil.- Ikonkaga
coloro'tkazmaslik β<Ionicons name="home" color="#000" />deb qattiq rang yozsangiz, faol/nofaol holat ishlamaydi. Navigatordan kelgancolorni uzating. - Matnni
<Text>siz qo'yish β RN'da har bir matn<Text>ichida bo'lishi SHART.<View>Bosh</View>xato;<View><Text>Bosh</Text></View>to'g'ri. - Modal options'ni ekranga qo'yish β
presentation: 'modal'ekran faylida emas, balki uni e'lon qilganStack.Screenoptions'ida yoziladi.
Xulosa¶
- Tab navigatsiya β pastdagi tugmalar qatori (Bosh/Qidiruv/Profil) orqali ilovaning asosiy bo'limlari orasida o'tish. Mobil ilovalarning eng keng tarqalgan naqshi. Layout:
app/(tabs)/_layout.tsxda<Tabs>+ har ekran uchun<Tabs.Screen name="..." />. - SDK 56'da
Tabsniexpo-router/js-tabsdan import qiling β eski'expo-router'yo'li eskirgan (deprecated) hisoblanadi. - Route guruhi
(tabs)β qavs ichidagi papka URL'ga qo'shilmaydi, faqat fayllarni tashkil etish va umumiy layout berish uchun.app/(tabs)/index.tsx->/,/tabs/indexemas. - Tab ikonkalari β
@expo/vector-icons(Ionicons) bilan:tabBarIcon: ({ color, size }) => <Ionicons name="home" size={size} color={color} />. Rangni navigatordan oling β faol/nofaol avtomatik ranglanadi. Yana:tabBarLabel,tabBarBadge,tabBarActiveTintColor. - Drawer (yon menyu) β
expo-router/drawerdan<Drawer>.gesture-handler+reanimatedga tayanadi (SDK 56 shablonida tayyor; alohida@react-navigation/drawershart emas). Ko'p yoki ikkinchi darajali bo'limlar uchun; tuzilishi Tabs'ga o'xshash (drawer...prefiksi). - Tabs vs Drawer β 3-5 asosiy bo'lim -> Tabs (doimo ko'rinadi); ko'p/qo'shimcha bo'lim -> Drawer (yashirin). Katta ilovalar ikkalasini birga ishlatishi mumkin.
- Nested navigatorlar β ildiz Stack ichida
(tabs)Tabs, tab ichida yana Stack (push uchun). Bino-qavat-xona naqshi. Eng mustahkam tuzilish β ildizda Stack, uning bitta "ekrani" o'rnida(tabs). - Modal ekran β
app/modal.tsx+Stack.Screendapresentation: 'modal'. Tablardan tashqarida, ustiga chiqadi;router.push('/modal')ochadi,router.back()yopadi.
Amaliy mashqlar¶
-
Ikki tabli ilova (oson).
app/(tabs)/_layout.tsxda ikki tabli (indexvasozlamalar) menyu yarating. Har birigatitlebering. Ekranlarni yozing β markazda tab nomini ko'rsatsin. Expo Go'da ikki tab orasida o'tib ko'ring. -
Ikonkalar qo'shing (oson). Yuqoridagi ilovaga
@expo/vector-iconsdanIoniconsikonka qo'shing (home,settings).tabBarActiveTintColorni o'zingiz tanlagan rangga sozlang. Nofaol holatda-outline, faol holatda to'la variant ko'rsatishga harakat qiling (maslahat:optionsfunksiya bo'lishi vafocusedni ishlatishi mumkin). -
Drawer'ga o'tkazing (o'rta). 2-mashqdagi ilovani Tabs o'rniga Drawer bilan qayta yozing: ildiz
app/_layout.tsxda<Drawer>, ichida<Drawer.Screen>.drawerIconvadrawerLabelqo'shing. Chetdan surib ochilishini sinang. Qaysi naqsh bu ilovaga ko'proq mos kelishini o'ylab ko'ring. -
Tabda detalga push (o'rta). Uch tabli ilova qiling. Bosh tabda 4-5 elementli
FlatListro'yxat bo'lsin. Element bosilgandarouter.push('/element/...')bilan detal sahifasiga o'ting. Detal sahifasini ildiz Stack'gaStack.Screensifatida qo'shing. ("Orqaga" tugmasi avtomatik ishlaydi.) -
Modal + badge (qiyin). To'liq misoldagi ilovaga: (a) suzuvchi tugma (FAB) bosilganda
app/modal.tsxmodalini oching; (b) modalda matn kiritib "Saqlash" bosilsa, profil tabidagitabBarBadgeqiymati birga oshsin. Maslahat: badge soni global holatda (Context yoki Zustand β 19-bob) saqlanishi kerak, chunki uni boshqa ekrandan o'zgartiryapsiz. Hozircha sodda Context bilan urinib ko'ring.
β¬ οΈ Oldingi: 14 β Expo Router asoslari Β· π Kitob boshi Β· Keyingi: 16 β Dinamik marshrut va parametrlar β‘οΈ