Zustand — global holat (savat, sozlamalar)
Muammodan boshlaylik
Savat:
- Mahsulot ekranidagi "Savatga qo'shish" tugmasi uni o'zgartiradi
- Tab panel badge si mahsulotlar sonini ko'rsatadi
- Savat ekrani ro'yxatni va jami summani ko'rsatadi
- Katalogdagi har bir kartada "savatda: 2" belgisi
To'rtta turli joy — bitta ma'lumot. Props orqali uzatish — _layout dan to'rt qavat pastga. Context — har bir o'zgarishda uni ishlatadigan barcha komponentlar qayta chiziladi.
Kerak — oddiy, tez global holat. Zustand — eng ixcham yechimlardan biri.
O'rnatish va store
npm install zustand
// stores/savat.ts
import { create } from 'zustand';
type SavatQatori = { id: number; nom: string; narx: number; rasm: string; soni: number };
type SavatHolati = {
qatorlar: SavatQatori[];
qosh: (m: Omit<SavatQatori, 'soni'>) => void;
kamaytir: (id: number) => void;
ochir: (id: number) => void;
tozala: () => void;
};
export const useSavat = create<SavatHolati>()((set) => ({
qatorlar: [],
qosh: (m) =>
set((s) => {
const bor = s.qatorlar.find((q) => q.id === m.id);
return {
qatorlar: bor
? s.qatorlar.map((q) => (q.id === m.id ? { ...q, soni: q.soni + 1 } : q))
: [...s.qatorlar, { ...m, soni: 1 }],
};
}),
kamaytir: (id) =>
set((s) => ({
qatorlar: s.qatorlar
.map((q) => (q.id === id ? { ...q, soni: q.soni - 1 } : q))
.filter((q) => q.soni > 0),
})),
ochir: (id) => set((s) => ({ qatorlar: s.qatorlar.filter((q) => q.id !== id) })),
tozala: () => set({ qatorlar: [] }),
}));
create— store yaratadi va hook qaytaradiset— holatni yangilash; qaytarilgan ob'ekt mavjud holat bilan birlashtiriladi- Provider kerak emas — hook istalgan joyda ishlaydi
Ishlatish — selektorlar bilan
// Mahsulot ekrani
const qosh = useSavat((s) => s.qosh);
<Tugma matn="Savatga qo'shish" onPress={() => qosh({ id: m.id, nom: m.nom, narx: m.narx, rasm: m.rasm })} />
// Tab badge
const soni = useSavat((s) => s.qatorlar.reduce((j, q) => j + q.soni, 0));
<Tabs.Screen name="savat" options={{ tabBarBadge: soni || undefined }} />
// Katalog kartasi
const savatdagi = useSavat((s) => s.qatorlar.find((q) => q.id === mahsulot.id)?.soni ?? 0);
Selektor — useSavat((s) => s.xxx) — komponent faqat o'ziga kerakli qism o'zgarganda qayta chiziladi. Tab badge faqat son o'zgarganda; boshqa mahsulot kartasi — faqat o'z mahsuloti soni o'zgarganda.
[!WARNING]
const savat = useSavat()— selektorsiz — butun store ni oladi va har qanday o'zgarishda qayta chiziladi. Doim selektor ishlating.
Bir nechta qiymat
import { useShallow } from 'zustand/react/shallow';
const { qatorlar, kamaytir, ochir } = useSavat(
useShallow((s) => ({ qatorlar: s.qatorlar, kamaytir: s.kamaytir, ochir: s.ochir }))
);
useShallow — ob'ekt har safar yangi yaratilgani uchun cheksiz qayta chizishning oldini oladi.
Savat ekrani
export default function SavatEkran() {
const qatorlar = useSavat((s) => s.qatorlar);
const { qosh, kamaytir, ochir } = useSavat(useShallow((s) => ({ qosh: s.qosh, kamaytir: s.kamaytir, ochir: s.ochir })));
const jami = qatorlar.reduce((j, q) => j + q.narx * q.soni, 0);
if (qatorlar.length === 0) {
return <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}><Text>Savat bo'sh</Text></View>;
}
return (
<View style={{ flex: 1 }}>
<FlatList
data={qatorlar}
keyExtractor={(q) => String(q.id)}
renderItem={({ item }) => (
<View style={{ flexDirection: 'row', alignItems: 'center', padding: 12, gap: 12 }}>
<Image source={{ uri: item.rasm }} style={{ width: 56, height: 56, borderRadius: 8 }} />
<View style={{ flex: 1 }}>
<Text numberOfLines={1}>{item.nom}</Text>
<Text style={{ fontWeight: '700' }}>{(item.narx * item.soni).toLocaleString()} so'm</Text>
</View>
<Pressable onPress={() => kamaytir(item.id)} hitSlop={8}><Ionicons name="remove-circle-outline" size={26} /></Pressable>
<Text>{item.soni}</Text>
<Pressable onPress={() => qosh(item)} hitSlop={8}><Ionicons name="add-circle-outline" size={26} /></Pressable>
</View>
)}
/>
<View style={{ padding: 16, borderTopWidth: 1, borderColor: '#e2e8f0' }}>
<Text style={{ fontSize: 18, fontWeight: '700' }}>Jami: {jami.toLocaleString()} so'm</Text>
<Tugma matn="Rasmiylashtirish" onPress={() => router.push('/rasmiylashtirish')} />
</View>
</View>
);
}
Komponentdan tashqarida
// Oddiy funksiyada (masalan, API javobidan keyin)
useSavat.getState().tozala();
const hozirgi = useSavat.getState().qatorlar;
Buyurtma muvaffaqiyatli — savatni tozalash, useMutation ning onSuccess ida.
Saqlash: persist
Ilova yopilsa — savat yo'qoladi. persist middleware — avtomatik saqlash:
npx expo install @react-native-async-storage/async-storage
import { persist, createJSONStorage } from 'zustand/middleware';
import AsyncStorage from '@react-native-async-storage/async-storage';
export const useSavat = create<SavatHolati>()(
persist(
(set) => ({
// ... yuqoridagi barcha kod
}),
{
name: 'savat',
storage: createJSONStorage(() => AsyncStorage),
partialize: (s) => ({ qatorlar: s.qatorlar }), // faqat ma'lumotni saqlash
}
)
);
Ilovani yopib ochsangiz — savat joyida. AsyncStorage haqida — keyingi darsda.
Sozlamalar store
// stores/sozlamalar.ts
type Sozlamalar = {
mavzu: 'tizim' | 'yorug' | 'qorongi';
til: 'uz' | 'ru' | 'en';
bildirishnoma: boolean;
ozgartir: (o: Partial<Omit<Sozlamalar, 'ozgartir'>>) => void;
};
export const useSozlamalar = create<Sozlamalar>()(
persist(
(set) => ({
mavzu: 'tizim',
til: 'uz',
bildirishnoma: true,
ozgartir: (o) => set(o),
}),
{ name: 'sozlamalar', storage: createJSONStorage(() => AsyncStorage) }
)
);
Qaysi holat qayerda
| Holat turi | Misol | Vosita |
|---|---|---|
| Server holati | Mahsulotlar, profil, buyurtmalar | TanStack Query (32) |
| Global klient holati | Savat, sozlamalar, mavzu | Zustand |
| Lokal holat | Modal ochiqmi, input qiymati, tanlangan tab | useState |
| Forma holati | Ro'yxatdan o'tish maydonlari | React Hook Form (21) |
| URL holati | Filtr, tanlangan ID | Router parametrlari (29) |
| Autentifikatsiya | Kirgan foydalanuvchi | Context yoki Zustand (30) |
Eng ko'p xato — hammasini bitta global store ga tiqish. Har bir turni o'z joyida saqlang.
Nima uchun shunday?
Nima uchun Zustand, Redux yoki Context emas?
Context — qiymatni uzatish uchun yaxshi, lekin u o'zgarganda barcha iste'molchilar qayta chiziladi — tez-tez o'zgaradigan savat uchun bu sekin. Redux — kuchli va qat'iy, lekin oddiy savat uchun ko'p tantanali kod (actions, reducers, slices). Zustand — o'rtadagi yechim: bitta funksiya bilan store, selektorlar orqali aniq qayta chizish, provider siz. Kichik va o'rta ilovalarning ko'pchiligida aynan shu muvozanat kerak.
Xulosa
Eslab qoling:
create<T>()((set) => ({ ...holat, ...amallar }))— store = hookset((s) => ({...}))— yangilash, birlashtiriladi- Doim selektor:
useSavat((s) => s.soni); bir nechta —useShallow useSavat.getState()— komponentdan tashqaridapersist+ AsyncStorage — avtomatik saqlash;partialize- Server holati — TanStack Query; global klient — Zustand; lokal —
useState
Keyingi dars
34-dars: AsyncStorage — qurilmada ma'lumot saqlash