Zustand — async action, middleware, devtools
Muammodan boshlaylik
57-darsdagi savat ishlaydi. Lekin:
- Sahifani yangilasangiz — savat bo'shab qoladi. Foydalanuvchi 10 ta mahsulot tanlagan edi
- Savatni rasmiylashtirish — serverga so'rov. Action async bo'lishi kerak
- Savat qandaydir noto'g'ri holatga tushdi — qaysi action, qachon, qanday o'zgartirdi — ko'rinmaydi
- Ichma-ich obyektlarni yangilash uchun
...spreadlar zanjiri o'qib bo'lmaydigan bo'lib ketdi
Zustand bularni middleware lar bilan hal qiladi: store ni o'rab, unga yangi imkoniyat qo'shadigan funksiyalar.
Async action lar
Zustand da async action uchun maxsus narsa kerak emas — oddiy async funksiya:
export const useBuyurtmaStore = create((set, get) => ({
holat: 'idle', // 'idle' | 'yuborilmoqda' | 'muvaffaqiyat' | 'xato'
xato: null,
rasmiylashtirish: async () => {
set({ holat: 'yuborilmoqda', xato: null });
try {
const mahsulotlar = useSavatStore.getState().mahsulotlar;
const j = await fetch('/api/buyurtmalar', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ mahsulotlar }),
});
if (!j.ok) throw new Error('Buyurtma qabul qilinmadi');
useSavatStore.getState().tozalash();
set({ holat: 'muvaffaqiyat' });
} catch (err) {
set({ holat: 'xato', xato: err.message });
}
},
}));
function Rasmiylashtirish() {
const holat = useBuyurtmaStore((s) => s.holat);
const rasmiylashtirish = useBuyurtmaStore((s) => s.rasmiylashtirish);
return (
<button onClick={rasmiylashtirish} disabled={holat === 'yuborilmoqda'}>
{holat === 'yuborilmoqda' ? 'Yuborilmoqda...' : 'Buyurtma berish'}
</button>
);
}
set ni await dan oldin ham, keyin ham chaqirish mumkin. Boshqa store ga getState() orqali murojaat qilinadi.
[!INFO] Server bilan ishlaydigan async action lar uchun avval TanStack Query ni o'ylang (56-dars):
useMutationyuklanish va xato holatlarini, qayta urinishni va kesh yangilashni o'zi qiladi. Zustand dagi async action — server ma'lumoti global client state ga ta'sir qilganda mos (masalan, login dan keyin token ni saqlash).
persist — localStorage ga saqlash
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
export const useSavatStore = create(
persist(
(set) => ({
mahsulotlar: [],
qoshish: (m) => set((s) => ({ mahsulotlar: [...s.mahsulotlar, m] })),
tozalash: () => set({ mahsulotlar: [] }),
}),
{
name: 'savat', // localStorage dagi kalit
}
)
);
Hammasi. Har bir o'zgarish localStorage ga yoziladi, sahifa ochilganda qaytariladi. 1-muammo hal.
Sozlamalar
import { persist, createJSONStorage } from 'zustand/middleware';
persist(
(set) => ({ ... }),
{
name: 'savat',
storage: createJSONStorage(() => sessionStorage), // tab yopilguncha
partialize: (state) => ({ mahsulotlar: state.mahsulotlar }), // faqat shu qismni saqlash
version: 2, // tuzilma o'zgarsa
migrate: (eski, versiya) => {
if (versiya < 2) {
return { mahsulotlar: eski.items ?? [] }; // eski nomni yangisiga
}
return eski;
},
}
)
partialize— vaqtinchalik holatlarni (yuklanmoqda,xato) saqlamaslik uchunversion+migrate— store tuzilmasi o'zgarganda eski foydalanuvchilarning saqlangan ma'lumotini moslashtirish
[!WARNING]
localStoragega maxfiy ma'lumot (parol, karta raqami) saqlamang — sahifadagi har qanday JavaScript (shu jumladan zararli kengaytma yoki XSS) uni o'qiy oladi. Auth token larni saqlash ham xavfsizlik nuqtai nazaridan bahsli; ko'p loyihalardahttpOnlycookie afzal ko'riladi.
devtools — Redux DevTools bilan
import { devtools } from 'zustand/middleware';
export const useSavatStore = create(
devtools(
(set) => ({
mahsulotlar: [],
qoshish: (m) =>
set(
(s) => ({ mahsulotlar: [...s.mahsulotlar, m] }),
false,
'savat/qoshish' // DevTools da action nomi
),
}),
{ name: 'SavatStore' }
)
);
Brauzerga Redux DevTools kengaytmasini o'rnating. Endi:
- Har bir o'zgarish nomi va vaqti bilan ro'yxatda
- Har qadamdagi state ni ko'rish
- "Vaqt bo'ylab sayohat" — oldingi holatga qaytish
set ning uchinchi argumenti — action nomi. Busiz DevTools da hammasi "anonymous" bo'lib ko'rinadi. 3-muammo hal.
immer — ichma-ich yangilash
Chuqur ichma-ich state ni yangilash:
// ❌ spread zanjiri
set((s) => ({
foydalanuvchi: {
...s.foydalanuvchi,
manzil: {
...s.foydalanuvchi.manzil,
shahar: 'Samarqand',
},
},
}));
immer middleware i bilan — xuddi o'zgartirayotgandek yozasiz, lekin natija baribir yangi obyekt:
npm install immer
import { immer } from 'zustand/middleware/immer';
export const useProfilStore = create(
immer((set) => ({
foydalanuvchi: { ism: 'Ali', manzil: { shahar: 'Toshkent' } },
shaharniOzgartir: (shahar) =>
set((state) => {
state.foydalanuvchi.manzil.shahar = shahar; // to'g'ridan-to'g'ri!
}),
}))
);
Immer "qoralama" (draft) yaratadi, siz uni o'zgartirasiz, u esa o'zgarishlar asosida yangi o'zgarmas obyekt yasaydi. 4-muammo hal.
Middleware larni birlashtirish
import { create } from 'zustand';
import { devtools, persist } from 'zustand/middleware';
import { immer } from 'zustand/middleware/immer';
export const useSavatStore = create(
devtools(
persist(
immer((set) => ({
mahsulotlar: [],
qoshish: (m) =>
set((s) => {
const bor = s.mahsulotlar.find((x) => x.id === m.id);
if (bor) bor.soni += 1;
else s.mahsulotlar.push({ ...m, soni: 1 });
}),
ochirish: (id) =>
set((s) => {
s.mahsulotlar = s.mahsulotlar.filter((x) => x.id !== id);
}),
})),
{ name: 'savat' }
),
{ name: 'SavatStore' }
)
);
Tavsiya etilgan tartib: devtools eng tashqarida — u barcha o'zgarishlarni ko'rishi uchun; immer eng ichkarida.
[!TIP]
persistishlatilganda server da (SSR — Next.js) va brauzerda boshlang'ich state farq qilishi mumkin: server bo'sh savat bilan render qiladi, brauzer esalocalStoragedan to'ldiradi — "hydration mismatch" ogohlantirishi. Oddiy Vite + React ilovada bu muammo yo'q.
Slice pattern — katta store ni bo'lish
Store katta bo'lib ketsa, uni mantiqiy qismlarga ajratish mumkin:
// store/slices/savatSlice.js
export const savatSlice = (set) => ({
mahsulotlar: [],
qoshish: (m) => set((s) => ({ mahsulotlar: [...s.mahsulotlar, m] })),
});
// store/slices/uiSlice.js
export const uiSlice = (set) => ({
yonPanelOchiq: false,
yonPanelniAlmashtir: () => set((s) => ({ yonPanelOchiq: !s.yonPanelOchiq })),
});
// store/index.js
export const useStore = create((...a) => ({
...savatSlice(...a),
...uiSlice(...a),
}));
Lekin ko'p hollarda alohida store lar (57-dars) soddaroq va yetarli.
Nima uchun shunday?
Nima uchun Zustand bu imkoniyatlarni o'ziga qurmasdan, alohida middleware sifatida beradi?
Chunki har bir loyihaga hammasi kerak emas. persist — faqat saqlanishi kerak bo'lgan store larga, immer — faqat chuqur ichma-ich state ga, devtools — faqat development da. Middleware — bu oddiy funksiya kompozitsiyasi: har biri store yaratuvchi funksiyani oladi va yangi, kengaytirilgan funksiya qaytaradi. Bu yondashuv kutubxonani kichik saqlaydi (keraksiz kod bundle ga kirmaydi) va sizga o'z middleware ingizni yozish imkonini beradi — masalan, har bir o'zgarishni analitikaga yuboradigan. Zustand ning falsafasi: minimal yadro, kerakli narsani o'zingiz qo'shasiz.
Xulosa
Eslab qoling:
- Async action — oddiy
asyncfunksiya;awaitdan oldin va keyinset - Server ma'lumotlari uchun avval TanStack Query ni o'ylang
persist— localStorage;name,partialize,version+migrate- Maxfiy ma'lumot localStorage ga emas
devtools— Redux DevTools;setning 3-argumenti — action nomiimmer— ichma-ich state ni "to'g'ridan-to'g'ri" yangilash- Tartib:
devtools(persist(immer(...))) - Katta store — slice pattern yoki alohida store lar
Keyingi dars
59-dars: Redux Toolkit — nima, Zustand dan farqi, qachon kerak