12.5-bob: Zustand
12-QISM — State Management va Data Fetching · 5-mavzu
1. Kirish va motivatsiya
12.1-bobda Context'ni, 12.2-bobda Redux Toolkit'ni o'rgandik. Context — oddiy, lekin selektor yo'q (re-render muammosi). Redux — kuchli, lekin biroz boilerplate va sozlama (store, Provider, slice) talab qiladi. Ikkalasining o'rtasida — yengil, oddiy, lekin kuchli bir vosita kerak edi: hech qanday Provider, kam kod, lekin tabiiy selektor va middleware bilan. Aynan shu — Zustand (nemischa "holat" degani — "ts-u-stand" deb o'qiladi). Bugun Zustand — client state management'ning eng tez o'sib borayotgan, eng yoqimli va eng ko'p tavsiya etiladigan yengil yechimi.
Zustand'ning falsafasi — minimalizm: "state management murakkab bo'lishi shart emas". Redux'ning butun kuchini (markaziy store, selektor, middleware, DevTools) beradi, lekin uning murakkabligisiz (Provider yo'q, action/reducer/dispatch boilerplate yo'q, configureStore yo'q). Siz bir necha qator bilan global store yaratasiz — to'g'ridan hook sifatida (useStore), va komponentlarda uni istalgan joydan ishlatasiz (Provider'siz). Va eng muhimi — u tabiiy selektor (useStore(s => s.count) — faqat kerakli qism o'zgarganda render — Context'ning asosiy cheklovini hal qiladi) va kuchli middleware (persist, devtools, immer) bilan keladi. Zamonaviy frontend tendensiyasi — TanStack Query (server state) + Zustand (kichik client state) — yengil, kuchli, kam kod kombinatsiyasi.
Bu bob: nega Zustand (Redux/Context cheklovi — yengil alternativa), Zustand nima (minimal, hook-asosli store), create (store yaratish), state va actions (bir joyda), selektor (faqat kerakli qism — re-render — eng muhim), shallow (ko'p qism tanlash), set/get (yangilash, oldingi qiymat), middleware (persist/devtools/immer), async actions (Zustand'da), store'ni bo'lish (slices pattern), TypeScript bilan Zustand, Zustand vs Redux vs Context (qachon qaysi — yakuniy qaror), va chuqurroq mavzular: transient update (render'siz — 2.12), vanilla store (React'siz — 2.13), subscribeWithSelector/combine/computed 2.14-bob, SSR/Next.js 2.15-bob, test 2.16-bob, Zustand vs Jotai vs Valtio 2.17-bob. Zustand ko'pincha "qo'shimcha vosita" deb qaraladi — bu bobda uni to'liq, amaliy va zamonaviy holatda ochamiz.
O'xshatish: Zustand — bu umumiy ish daftarchasi (Redux — butun byurokratik bank tizimi — 12.2). Redux'da pulni o'zgartirish uchun rasmiy so'rov (action) yozasiz, operatorga (reducer) berasiz, hammasi qaydlanadi (DevTools) — bu katta tizim uchun zarur, lekin ko'p qog'oz. Context — devordagi e'lon taxtasi (hamma ko'radi, lekin biror narsani o'zgartirsa hamma e'tibor beradi — re-render). Zustand — bu stol ustidagi umumiy daftarcha: istalgan xodim (komponent) unga to'g'ridan yozadi (action) va o'qiydi (selektor) — Provider (qabulxona), boilerplate (rasmiy so'rov) yo'q. Lekin daftarcha aqlli: siz faqat o'zingiz o'qiyotgan sahifani (selektor) kuzatasiz — boshqa sahifa o'zgarsa, sizni bezovta qilmaydi (kerakli render). Va daftarchani saqlab qo'ysangiz (persist), kompyuter o'chib yoqilsa ham yozuvlar qoladi. Oddiy daftarcha, lekin katta tizim imkoniyatlari bilan.
Nega muhim?
- Yengil, kam kod — Zustand Redux'ning kuchini minimal kod bilan beradi (Provider/boilerplate yo'q).
- Zamonaviy tendensiya — TanStack Query (server) + Zustand (client) — eng ko'p tavsiya etiladigan kombinatsiya.
- Stack'da bor — Zustand zamonaviy frontend stack'ining tavsiya etiladigan qismi.
- Context muammolarini hal qiladi — tabiiy selektor (re-render), middleware (persist) — Context'dan kuchliroq.
2. Nazariya — chuqur tushuntirish
2.1. Nega Zustand — Redux va Context o'rtasi
CONTEXT 12.1-bob — oddiy, lekin:
selektor yo'q (re-render — 12.1: 2.3)
middleware yo'q (persist/devtools qo'lda)
value memo, provider hell (boilerplate'ning o'z turi)
REDUX 12.2-bob — kuchli, lekin:
boilerplate (store, Provider, slice, configureStore)
sozlash ko'p (middleware, typed hooks)
kichik loyihaga "og'ir"
ZUSTAND — ikkalasining yaxshilarini oladi:
Yengil (kam kod — bir necha qator store)
Provider YO'Q (to'g'ridan hook — useStore)
Tabiiy selektor (faqat kerakli qism — re-render — Context yechimi)
Middleware (persist/devtools/immer — Redux'dek)
TypeScript do'st
Zustand — Redux kuchi + Context oddiyligi (Provider/boilerplate yo'q)
Kichik/o'rta client state uchun ideal (katta murakkab hali Redux mumkin)Nega Zustand — Context va Redux'ning o'rtasidagi bo'shliqni to'ldiradi. Context 12.1-bob — oddiy, lekin selektor yo'q (re-render muammosi — 12.1: 2.3), middleware yo'q (persist/devtools qo'lda), va value memo/provider hell (o'z boilerplate'i). Redux 12.2-bob — kuchli, lekin boilerplate (store, Provider, slice, configureStore), sozlash ko'p (middleware, typed hooks), kichik loyihaga "og'ir". Zustand — ikkalasining yaxshi tomonlarini oladi: (1) yengil (kam kod — bir necha qatorda store); (2) Provider yo'q (to'g'ridan hook —
useStore— istalgan joydan, o'rab oluvchi Provider kerak emas); (3) tabiiy selektor (faqat kerakli qism o'zgarganda render — Context'ning asosiy cheklovini hal qiladi); (4) middleware (persist/devtools/immer— Redux'dek kuchli); (5) TypeScript do'st. Ikki nuqta: (1) Zustand — Redux'ning kuchi (selektor, middleware, markaziy store) + Context'ning oddiyligi (Provider/boilerplate yo'q); (2) kichik/o'rta client state uchun ideal (katta, juda murakkab, qat'iy struktura kerak bo'lgan jamoaviy loyihada hali Redux mumkin — lekin Zustand ham yaxshi masshtablanadi). Zustand — "minimal, lekin yetarli" falsafasining timsoli.
2.2. Zustand nima va create
ZUSTAND — minimal, hook-asosli global state kutubxonasi:
npm install zustand
create — store yaratadi (state + actions BIR joyda, to'g'ridan hook):
import { create } from "zustand";
const useCounterStore = create((set) => ({
count: 0, // state
increment: () => set((s) => ({ count: s.count + 1 })), // action (set bilan)
decrement: () => set((s) => ({ count: s.count - 1 })),
reset: () => set({ count: 0 }),
}));
// Ishlatish — to'g'ridan hook (PROVIDER YO'Q!):
function Counter() {
const count = useCounterStore((s) => s.count); // selektor 2.5-bob
const increment = useCounterStore((s) => s.increment);
return <button onClick={increment}>{count}</button>;
}
┌────────────────────────────────────────────────────────────┐
│ create((set) => ({ state, actions })) useStore hook │
│ Provider YO'Q, configureStore YO'Q, boilerplate YO'Q │
└────────────────────────────────────────────────────────────┘
create — state va actions BIR joyda (slice/reducer/action ajratish yo'q)
useStore — to'g'ridan hook (Provider'siz — istalgan komponentda)Zustand nima va create — minimal, hook-asosli global state (
npm install zustand). Markaziy funksiya —create: u store yaratadi va to'g'ridan hook qaytaradi.create((set) => ({ count: 0, increment: () => set((s) => ({ count: s.count + 1 })) }))— funksiyaset(state'ni yangilash) ni oladi va obyekt qaytaradi: ichida state (count: 0) va actions (increment—setbilan yangilaydi) — bir joyda (Redux'dek slice/reducer/action ajratishsiz). Ishlatish eng oddiy qism: komponentda to'g'ridan hook (useCounterStore) — Provider yo'q! (useCounterStore((s) => s.count)— selektor bilan kerakli qismni oladi — 2.5). Ikki nuqta: (1)create— state va actions bir joyda (setbilan o'zgartirish — minimal struktura, Redux'ning slice/reducer/action/dispatch ajratishi yo'q); (2)useStore— to'g'ridan hook (Provider'siz — ilovani o'rab qo'yish kerak emas, istalgan komponentda to'g'ridan chaqirasiz). Bu — Zustand'ning asosiy soddaligi: store yaratish va ishlatish — ikki oddiy qadam (kam kod, kam tushuncha).
2.3. State, actions va set/get
STATE va ACTIONS — store ICHIDA birga (set bilan o'zgartirish):
const useStore = create((set, get) => ({
// STATE:
items: [],
filter: "all",
// ACTIONS (set bilan yangilash):
addItem: (item) => set((s) => ({ items: [...s.items, item] })), // oldingi state'dan (s)
setFilter: (filter) => set({ filter }), // to'g'ridan qiymat
clearItems: () => set({ items: [] }),
// get — boshqa qiymat/action'ni O'QISH (action ichida):
getVisibleCount: () => {
const { items, filter } = get(); // joriy state'ni o'qi
return items.filter((i) => filter === "all" || i.status === filter).length;
},
}));
SET — ikki shakl:
set((s) => ({ count: s.count + 1 })) // funksiya (oldingi state'dan — afzal)
set({ count: 5 }) // obyekt (to'g'ridan qiymat)
set — state'ni yangilaydi (qisman — faqat berilgan maydon merge qilinadi)
get — action ICHIDA joriy state'ni o'qish (boshqa action chaqirish ham)State, actions va set/get — Zustand store'ining ichki tuzilishi.
create((set, get) => ({...}))— funksiyaset(state'ni yangilash) vaget(state'ni o'qish) ni oladi. State va actions store ichida birga: state (items: [],filter: "all"), actions (addItem: (item) => set((s) => ({ items: [...s.items, item] }))—setbilan).setikki shaklda ishlatiladi: funksiya (set((s) => ({ count: s.count + 1 }))— oldingi state'dan hisoblash — afzal, xuddiuseStatening updater'i — 11.4: 2.6) yoki obyekt (set({ filter })— to'g'ridan yangi qiymat). Muhim:setstate'ni qisman yangilaydi (faqat berilgan maydon o'zgaradi, qolgani saqlanadi — avtomatik merge, Redux'ning{...state, ...}o'rniga).get— action ichida joriy state'ni o'qish uchun (const { items, filter } = get()— masalan hisoblangan qiymat, yoki boshqa action'ni chaqirish). Ikki nuqta: (1)set— state'ni yangilaydi (qisman merge — faqat berilgan maydon — bir qism yangilash uchun qulay); funksiya shakli (oldingi state'dan) afzal; (2)get— action ichida joriy state'ni o'qish (yoki boshqa action chaqirish — action'lar bir-birini ishlata oladi). Bu — Zustand'ning minimal, lekin to'liq state-yangilash mexanizmi.
2.4. Selektor — faqat kerakli qism (re-render)
SELEKTOR — store'dan FAQAT KERAKLI qismni olish (re-render optimizatsiyasi — eng muhim):
Butun store'ni olish (har o'zgarishda render — Context muammosidek):
const store = useStore(); // butun store har o'zgarishda render
Selektor — faqat kerakli qism (faqat o'sha qism o'zgarganda render):
const count = useStore((s) => s.count); // FAQAT count o'zgarganda render
const filter = useStore((s) => s.filter); // filter o'zgarsa — count komponenti render BO'LMAYDI
bu Context'ning ASOSIY CHEKLOVINI (selektor yo'q) hal qiladi (12.1: 2.9)
┌────────────────────────────────────────────────────────────┐
│ useStore(): butun store (har o'zgarishda render — yomon) │
│ useStore(s => s.count): faqat count (kerakli render — afzal)│
└────────────────────────────────────────────────────────────┘
DOIM selektor bilan (useStore(s => s.x)) — butun store'ni olmang (re-render)
Selektor — Zustand'ning Context'dan eng katta afzalligi (tabiiy, oddiy)Selektor — faqat kerakli qism — Zustand'ning eng muhim xususiyati va Context'dan eng katta afzalligi. Selektor — store'dan faqat kerakli qismni olish funksiyasi:
const count = useStore((s) => s.count)— komponent faqatcounto'zgarganda re-render bo'ladi; agarfiltero'zgarsa,countni ishlatgan komponent render bo'lmaydi. Bu — Context'ning asosiy cheklovini (selektor yo'q — butun value, har o'zgarishda render — 12.1: 2.9) tabiiy hal qiladi. Yomon yondashuv:const store = useStore()(selektorsiz — butun store'ni oladi) store'ning har qismi o'zgarganda komponent render bo'ladi (Context muammosidek). Yaxshi yondashuv: doim selektor (useStore((s) => s.count)) — faqat o'sha qism. Ikki nuqta: (1) doim selektor bilan ishlating (useStore(s => s.x)) — butun store'ni olmang (selektorsizuseStore()— har o'zgarishda render — performance muammosi); (2) selektor — Zustand'ning Context'dan eng katta afzalligi (tabiiy, oddiy — Redux'ninguseSelector'idek, lekin boilerplate'siz). Bu — Zustand'ni performant va Context'dan yaxshiroq qilgan asosiy sabab.
2.5. Shallow va ko'p qism tanlash
MUAMMO: selektor OBYEKT/MASSIV qaytarsa — har render yangi havola har render render:
const { count, name } = useStore((s) => ({ count: s.count, name: s.name }));
// har render YANGI obyekt ({count, name}) har render render (referential — 11.6: 2.9)
YECHIM 1 — alohida selektorlar (eng oddiy):
const count = useStore((s) => s.count); // primitiv — referential muammo yo'q
const name = useStore((s) => s.name);
YECHIM 2 — useShallow (ko'p qism, yuzaki taqqoslash):
import { useShallow } from "zustand/react/shallow";
const { count, name } = useStore(useShallow((s) => ({ count: s.count, name: s.name })));
// useShallow yuzaki solishtiradi (count/name o'zgarmasa — render yo'q)
┌────────────────────────────────────────────────────────────┐
│ Bitta primitiv oddiy selektor | Ko'p qism useShallow │
│ (obyekt qaytaradigan selektor — useShallow shart — 11.6: 2.9)│
└────────────────────────────────────────────────────────────┘
Obyekt/massiv qaytaradigan selektor useShallow (aks holda har render render)
Bitta primitiv qiymat oddiy selektor (useShallow shart emas)Shallow va ko'p qism tanlash — selektorning referential tuzog'ini hal qiladi (11.6: 2.9 davomi). Muammo: agar selektor obyekt yoki massiv qaytarsa (
useStore((s) => ({ count: s.count, name: s.name }))— ko'p qismni bir vaqtda olish), u har render yangi havola yaratadi Zustand uni "o'zgardi" deb biladi komponent har render re-render bo'ladi (referential equality — 11.6: 2.9). Yechim 1 — alohida selektorlar (eng oddiy):const count = useStore((s) => s.count); const name = useStore((s) => s.name)— har biri primitiv qiymat qaytaradi (referential muammo yo'q). Yechim 2 —useShallow(ko'p qismni bir selektorda olish kerak bo'lganda):useStore(useShallow((s) => ({ count: s.count, name: s.name })))—useShallowqaytargan obyektni yuzaki (shallow) solishtiradi (count/nameqiymatlari o'zgarmasa — yangi obyekt bo'lsa ham render qilmaydi). Ikki nuqta: (1) obyekt/massiv qaytaradigan selektoruseShallow(aks holda har render render — eng keng Zustand tuzog'i); (2) bitta primitiv qiymat oddiy selektor (useShallowshart emas — primitiv===bilan to'g'ri solishtiriladi). Amalda ko'pincha alohida primitiv selektorlar afzal (oddiy, aniq); ko'p qism kerak bo'lgandauseShallow. Bu — Zustand'ni performant ishlatishning zarur bilimi.
2.6. Middleware — persist, devtools, immer
ZUSTAND MIDDLEWARE — store'ni "o'rab", qo'shimcha imkoniyat (Redux'dek):
// PERSIST — state'ni localStorage'da SAQLASH (sahifa yangilansa qoladi — 11.7):
import { persist } from "zustand/middleware";
const useStore = create(persist(
(set) => ({ count: 0, increment: () => set((s) => ({ count: s.count + 1 })) }),
{ name: "counter-storage" } // localStorage kaliti
));
// count localStorage'da saqlanadi (yangilansa ham qoladi)
// DEVTOOLS — Redux DevTools'da kuzatish (vaqt sayohati — 12.2: 2.8):
import { devtools } from "zustand/middleware";
const useStore = create(devtools((set) => ({...}), { name: "MyStore" }));
// IMMER — mutatsiya sintaksisi (immutable — RTK'dek — 12.2: 2.5):
import { immer } from "zustand/middleware/immer";
const useStore = create(immer((set) => ({
items: [],
addItem: (item) => set((s) => { s.items.push(item); }), // push — Immer (mutatsiya OK)
})));
// Middleware'larni STACK qilish (birga):
create(devtools(persist(immer((set) => ({...})), { name: "x" })));
persist — localStorage saqlash; devtools — kuzatish; immer — mutatsiya sintaksisi
Middleware'lar stack qilinadi (devtools(persist(immer(...))) — birga ishlaydi)Middleware — persist, devtools, immer — Zustand store'iga qo'shimcha imkoniyatlar (Redux'dek kuchli). Asosiy middleware'lar: (1)
persist— state'nilocalStorageda (yoki sessionStorage) saqlash (sahifa yangilansa qoladi — 11.7useLocalStoragening store darajasidagi versiyasi):persist((set) => ({...}), { name: "counter-storage" })—namelocalStorage kaliti; theme, auth, savat kabi saqlanishi kerak bo'lgan state uchun ideal. (2)devtools— Redux DevTools'da kuzatish (vaqt sayohati — Zustand store'ni ham Redux DevTools'da ko'rish — 12.2: 2.8):devtools((set) => ({...}), { name: "MyStore" }). (3)immer— mutatsiya sintaksisi (immutable natija — RTK'dek — 12.2: 2.5):immer((set) => ({ addItem: (item) => set((s) => { s.items.push(item) }) }))—push(mutatsiya) ishlatsa bo'ladi (Immer immutable qiladi). Middleware'larni stack qilish mumkin (birga):devtools(persist(immer((set) => ({...})))). Ikki nuqta: (1)persist— localStorage saqlash;devtools— kuzatish;immer— mutatsiya sintaksisi (ichma-ich state uchun qulay); (2) middleware'lar stack qilinadi (devtools(persist(immer(...)))— birga ishlaydi, lekin tartib muhim — devtools tashqarida). Bu — Zustand'ni minimal qoldirgan holda (kerakli imkoniyatni qo'shish) Redux darajasidagi kuchga yetkazadi.
2.7. Async actions
ZUSTAND'da ASYNC action — to'g'ridan (Redux thunk kabi murakkablik yo'q):
const useStore = create((set, get) => ({
users: [],
loading: false,
error: null,
// Async action — to'g'ridan async funksiya (set bilan holatlar):
fetchUsers: async () => {
set({ loading: true, error: null });
try {
const res = await fetch("/api/users");
const users = await res.json();
set({ users, loading: false }); // muvaffaqiyat
} catch (err) {
set({ error: err.message, loading: false }); // xato
}
},
}));
// Ishlatish:
const fetchUsers = useStore((s) => s.fetchUsers);
useEffect(() => { fetchUsers(); }, []);
Async action — to'g'ridan async funksiya (createAsyncThunk murakkabligi yo'q — 12.2: 2.7)
LEKIN: server ma'lumoti uchun TanStack Query AFZAL (kesh/sync — 12.4); Zustand — client stateAsync actions — Zustand'da asinxron amallar (to'g'ridan, Redux thunk murakkabligisiz). Zustand'da async action shunchaki async funksiya:
fetchUsers: async () => { set({ loading: true }); try { const res = await fetch(...); set({ users: await res.json(), loading: false }) } catch (err) { set({ error: err.message, loading: false }) } }—setbilan loading/error/data holatlarini boshqarasiz (Redux'ningcreateAsyncThunk+extraReducersmurakkabligisiz — 12.2: 2.7 — bir oddiy funksiya). IshlatishdauseEffectda chaqirasiz yoki tugmaga bog'laysiz. Lekin muhim ogohlantirish (bu butun 12-QISM'ning markaziy g'oyasi): server ma'lumoti uchun Zustand'ning async action'i ishlab bo'ladi, lekin TanStack Query (12.4) ancha afzal — chunki Zustand async action keshlamaydi, fonda yangilamaydi, qayta urinmaydi, eskirishni boshqarmaydi (server state'ning barcha muammosini qo'lda — 12.3: 2.1). Zustand — client state uchun (theme, modal, lokal savat, UI holati); server ma'lumoti TanStack Query. Ikki nuqta: (1) Zustand async action — to'g'ridan (oddiy); (2) lekin server state TanStack Query (Zustand — client state). Bu farqni eslab qoling — Zustand'ni server ma'lumotiga ishlatmang (TanStack Query bor).
2.8. Store'ni bo'lish — slices pattern
KATTA STORE'ni BO'LISH — har feature alohida "slice" (modullik):
// Har slice — alohida funksiya (state + actions):
const createUserSlice = (set) => ({
user: null,
setUser: (user) => set({ user }),
logout: () => set({ user: null }),
});
const createCartSlice = (set) => ({
items: [],
addItem: (item) => set((s) => ({ items: [...s.items, item] })),
});
// Birlashtirish (bitta store, ko'p slice):
const useStore = create((...args) => ({
...createUserSlice(...args),
...createCartSlice(...args),
}));
// Ishlatish — bitta store, har slice'dan selektor:
const user = useStore((s) => s.user);
const items = useStore((s) => s.items);
┌────────────────────────────────────────────────────────────┐
│ Slices: har feature alohida funksiya bitta store'da birga │
│ (modullik — Redux'ning slice'iga o'xshash, lekin yengilroq) │
└────────────────────────────────────────────────────────────┘
Slices pattern — katta store'ni feature bo'yicha bo'lish (modullik, tartibli)
Bitta store, ko'p slice (Redux'ning combineReducers'dek, lekin oddiy)Store'ni bo'lish — slices pattern — katta Zustand store'ini tartibli saqlash. Store o'sganda (ko'p state va action) uni slice'larga bo'lish mumkin (har feature alohida): har slice — alohida funksiya (state + actions) —
const createUserSlice = (set) => ({ user: null, setUser: (u) => set({ user: u }), logout: () => set({ user: null }) }),const createCartSlice = (set) => ({ items: [], addItem: ... }). Keyin ularni birlashtirasiz (bitta store, ko'p slice):create((...args) => ({ ...createUserSlice(...args), ...createCartSlice(...args) })). Ishlatishda — bitta store, har slice'dan selektor (useStore((s) => s.user),useStore((s) => s.items)). Ikki nuqta: (1) slices pattern — katta store'ni feature bo'yicha bo'lish (modullik, tartibli — har feature alohida faylda —userSlice.ts,cartSlice.ts); (2) bitta store, ko'p slice (Redux'ningcombineReducers/slice'iga o'xshash, lekin ancha oddiy — boilerplate'siz). Bu — Zustand'ni katta loyihada ham tartibli saqlashning usuli (Zustand kichik loyihadan katta loyihagacha yaxshi masshtablanadi). TypeScript bilan slices — har slice o'z turiga ega (StateCreator — 2.9).
2.9. TypeScript bilan Zustand
ZUSTAND + TYPESCRIPT 11.14-bob:
interface CounterStore {
count: number;
increment: () => void;
decrement: () => void;
reset: () => void;
}
const useCounterStore = create<CounterStore>((set) => ({ // create<T> — tur
count: 0,
increment: () => set((s) => ({ count: s.count + 1 })),
decrement: () => set((s) => ({ count: s.count - 1 })),
reset: () => set({ count: 0 }),
}));
// Selektor — tur AVTOMATIK (autocomplete):
const count = useCounterStore((s) => s.count); // count: number (s — CounterStore)
// Slices bilan (StateCreator):
import { StateCreator } from "zustand";
const createUserSlice: StateCreator<Store, [], [], UserSlice> = (set) => ({...});
create<StoreType>((set) => ({...})) — tur bir marta (selektor avtomatik turlanadi)
Slices — StateCreator turi bilan (har slice type-safe)TypeScript bilan Zustand — type-safe store 11.14-bob. Store turi
interfaceda belgilanadi (interface CounterStore { count: number; increment: () => void; ... }), vacreate<CounterStore>((set) => ({...}))— turni generic argument sifatida berasiz (create<T>). Shundan keyin selektorlar avtomatik turlanadi:const count = useCounterStore((s) => s.count)—sCounterStoreturida (autocomplete:s.count,s.increment...), vacountnumberturida. Slices bilan 2.8-bob — har sliceStateCreatorturini ishlatadi (StateCreator<Store, [], [], UserSlice>) — har slice type-safe. Ikki nuqta: (1)create<StoreType>((set) => ({...}))— turni bir marta belgilaysiz (createda), keyin selektorlar avtomatik turlanadi (qo'lda qayta yozish kerak emas — Redux'ning typed hooks'idan soddaroq); (2) slices —StateCreatorturi bilan (har slice o'z turiga ega, type-safe). Zustand TypeScript bilan juda yaxshi ishlaydi (Redux'dan kamroq boilerplate —create<T>bir marta, qolgani avtomatik). Bu — type-safe, kam kodli global state.
2.10. Zustand vs Redux vs Context (yakuniy qaror)
CLIENT STATE VOSITASINI TANLASH (yakuniy — 12.1: 2.10 to'liq):
┌──────────────┬─────────────────────────────────────────────┐
│ Context │ kam o'zgaradigan global (theme/auth/locale) │
│ 12.1-bob │ — o'rnatilgan, oddiy; selektor yo'q │
│ Zustand │ kichik/o'rta client state (selektor, persist) │
│ (bu bob) │ — yengil, kam kod, Provider yo'q (KENG TAVSIYA)│
│ Redux (RTK) │ katta, murakkab, jamoaviy (DevTools, qat'iy) │
│ 12.2-bob │ — kuchli, lekin boilerplate │
│ TanStack/RTK │ SERVER ma'lumoti (kesh/sync — 12.3, 12.4) │
│ Query │ — client EMAS (server state — alohida) │
│ Jotai/Valtio │ atom (granular) / proxy (mutatsiya) — 2.17 │
│ (muqobil) │ — Zustand bilan bir oila; uslubga qarab │
└──────────────┴─────────────────────────────────────────────┘
ZAMONAVIY TENDENSIYA (2026):
TanStack Query (server state) + Zustand (kichik client state)
yengil, kuchli, kam kod (Redux ko'pincha keraksiz — server Query'da, client Zustand'da)
Yengil/o'rta client Zustand (eng keng tavsiya); katta jamoaviy Redux
Zamonaviy: TanStack Query + Zustand (Redux'siz — ko'p loyihaga yetadi)Zustand vs Redux vs Context — client state vositasini tanlashning yakuniy qarori (12.1: 2.10 to'liq). To'g'ri tanlov: Context 12.1-bob — kam o'zgaradigan global (theme/auth/locale) — o'rnatilgan, oddiy, lekin selektor yo'q; Zustand (bu bob) — kichik/o'rta client state (selektor, persist, middleware) — yengil, kam kod, Provider yo'q — eng keng tavsiya; Redux (RTK) 12.2-bob — katta, murakkab, jamoaviy ilova (DevTools, qat'iy struktura) — kuchli, lekin boilerplate; TanStack/RTK Query (12.3, 12.4) — server ma'lumoti (kesh/sync) — client emas (server state — alohida). Zamonaviy tendensiya (2026): TanStack Query (server state) + Zustand (kichik client state) — yengil, kuchli, kam kod kombinatsiyasi; bu ko'p loyihaga yetadi (Redux ko'pincha keraksiz — server ma'lumoti Query'da, client UI holati Zustand'da). Ikki yakuniy nuqta: (1) yengil/o'rta client state Zustand (eng keng tavsiya etiladigan — Redux'ning kuchi, Context'ning oddiyligi); katta, jamoaviy, qat'iy Redux; (2) zamonaviy stack — TanStack Query + Zustand (Redux'siz — ko'p loyihaga yetadi, kam kod, kuchli). Bu qaror — frontend arxitektura tanlovining cho'qqisi: har state turiga (client kam-o'zgaruvchi/kichik/katta, server) mos vosita. Zustand — bu spektrda eng yoqimli "o'rta" yechim.
2.11. set — merge, funksional, replace va get chuqurroq
SET ning uch xususiyati (2.3 davomi):
1) QISMAN MERGE (standart) — faqat berilgan maydon o'zgaradi, qolgani saqlanadi:
set({ filter: "done" }); // count, items, boshqa hammasi joyida qoladi
// Redux'ning {...state, filter} ni Zustand AVTOMATIK qiladi (yuqori daraja — 1 qavat)
2) FUNKSIONAL set — oldingi state'dan hisoblash (afzal — atomik):
set((prev) => ({ count: prev.count + 1 })); // ketma-ket chaqiriqlar to'g'ri
3) REPLACE (ikkinchi argument true) — merge EMAS, butun state'ni ALMASHTIRISH:
set({ count: 0 }, true); // actions ham o'chadi! (butun obyekt almashtiriladi)
// deyarli ishlatilmaydi; faqat butun state'ni to'liq reset qilishda ehtiyot bilan
merge — faqat 1-daraja (ichma-ich obyekt to'liq almashtiriladi — Immer yoki qo'lda spread)
set(obj, true) — REPLACE (butun state) — actions'ni ham o'chiradi, ehtiyot bo'lingset — merge, funksional, replace va get chuqurroq —
setning ichki xatti-harakati (2.3 davomi, ko'p tuzoq shu yerda). (1) Qisman merge (standart):set({ filter: "done" })faqatfilterni o'zgartiradi,count,itemsva boshqa hamma maydon saqlanadi — Zustand{ ...state, filter }ni avtomatik qiladi. Muhim cheklov: merge faqat birinchi darajada ishlaydi — ichma-ich obyektni (set({ user: { name } })) berilsa, butunuserobyekti almashtiriladi (user.emailyo'qoladi). Ichma-ich yangilash uchun qo'lda spread (set((s) => ({ user: { ...s.user, name } }))) yokiimmermiddleware 2.6-bob. (2) Funksionalset:set((prev) => ({ count: prev.count + 1 }))— oldingi state'dan hisoblaganda afzal (ketma-ket chaqiriqlarda to'g'ri natija, xuddiuseStatening updater'i — 11.4). (3) Replace flag:set(obj, true)— ikkinchi argumenttruebo'lsa, merge emas, butun state obyekti almashtiriladi (actions ham o'chib ketadi!) — deyarli ishlatilmaydi, faqat to'liq reset uchun ehtiyot bilan.get— action ichida joriy state'ni sinxron o'qish (get().items), boshqa action chaqirish (get().recalculate()), yoki derived qiymat hisoblash uchun. Ikki nuqta: (1) merge faqat 1-daraja — ichma-ich state'gaimmeryoki qo'lda spread; (2)set(obj, true)replace butun state'ni almashtiradi (actions'ni ham) — deyarli kerak emas.
2.12. Transient update — subscribe bilan re-render'siz yangilash
MUAMMO: tez-tez o'zgaradigan qiymat (mouse pozitsiyasi, scroll, animatsiya) selektor bilan
olinsa — har o'zgarishda RENDER (60fps 60 render/soniya sekin).
YECHIM: TRANSIENT UPDATE — subscribe + useRef (React render'sidan tashqarida):
function Cursor() {
const ref = useRef(null);
useEffect(() => {
// subscribe — store o'zgarishini render'siz kuzatish (to'g'ridan DOM'ga):
const unsub = useStore.subscribe((s) => {
if (ref.current) ref.current.style.transform =
`translate(${s.x}px, ${s.y}px)`; // DOM'ni to'g'ridan — render YO'Q
});
return unsub; // tozalash (12.x: useEffect cleanup)
}, []);
return <div ref={ref} />;
}
Transient — subscribe + ref: tez-tez o'zgaradigan qiymatni RENDER'SIZ qo'llash
Faqat vizual/tashqi effekt uchun (DOM/canvas); UI matni uchun oddiy selektorTransient update — subscribe bilan re-render'siz yangilash — juda tez o'zgaradigan qiymatlar uchun optimizatsiya. Muammo: mouse pozitsiyasi, scroll, animatsiya kabi sekundiga o'nlab marta o'zgaradigan qiymatni oddiy selektor (
useStore(s => s.x)) bilan olsangiz, har o'zgarishda komponent re-render bo'ladi (60fps soniyada 60 render sekin). Yechim — transient update:useStore.subscribe(listener)bilan store o'zgarishini React render tsiklidan tashqarida kuzatib, natijani to'g'ridan DOM'ga (ref.current.style...) yoki canvas'ga yozasiz — React umuman render qilmaydi.subscribeuseEffectichida ro'yxatga olinadi va cleanup'da (return unsub) bekor qilinadi. Ikki nuqta: (1) transient =subscribe+useRef— tez-tez o'zgaradigan qiymatni render'siz qo'llash (imperativ DOM manipulyatsiyasi); (2) faqat vizual/tashqi effekt uchun (kursor, chizma, progress) — oddiy UI matnini ko'rsatish uchun baribir oddiy selektor. Bu — Zustand'ning kam ma'lum, lekin performansda qudratli imkoniyati.
2.13. Vanilla store — Zustand'ni React'siz ishlatish
VANILLA STORE — React'ga bog'liq bo'lmagan store (getState/setState/subscribe):
import { createStore } from "zustand/vanilla";
const store = createStore((set) => ({ count: 0, inc: () => set((s) => ({ count: s.count + 1 })) }));
store.getState().count; // o'qish (hook'siz)
store.getState().inc(); // action
store.setState({ count: 10 }); // to'g'ridan yozish
const unsub = store.subscribe((s) => console.log(s.count)); // kuzatish
// React komponentida ishlatish — useStore(store, selector) bilan bog'lash:
import { useStore } from "zustand";
const count = useStore(store, (s) => s.count);
┌────────────────────────────────────────────────────────────┐
│ create(...) React hook (useStore) o'zi │
│ createStore(...) vanilla (getState/setState/subscribe) │
│ create ichida vanilla store BOR — .getState() har doim mavjud│
└────────────────────────────────────────────────────────────┘
create — React hook; createStore (vanilla) — React'siz (Node, test, non-React)
create'ning natijasi ham vanilla API'ga ega: useStore.getState()/.subscribe()Vanilla store — Zustand'ni React'siz ishlatish — Zustand yadrosi React'ga bog'liq emas.
createReact hook qaytaradi, lekin ostidagi mexanizm — vanilla store (getState/setState/subscribe). Sof vanilla store'niimport { createStore } from "zustand/vanilla"bilan yaratasiz — u faqatstore.getState(),store.setState(),store.subscribe()beradi (React yo'q — Node.js, test, oddiy JS, yoki boshqa framework'da ishlaydi). Uni React'ga bog'lash uchunuseStore(store, (s) => s.count)(importzustanddan). Amaliy jihat:createbilan yaratilgan hook ham vanilla API'ga ega —useCounterStore.getState(),useCounterStore.setState(),useCounterStore.subscribe()(Misol 10 — interceptor, util, event handler'da hook'siz ishlatish). Ikki nuqta: (1)create— React hook,createStore(zustand/vanilla) — React'siz vanilla (test/Node/non-React); (2)createning natijasi ham.getState()/.setState()/.subscribe()ga ega (komponent tashqarisida to'liq foydalanish mumkin). Bu — Zustand'ni test qilish (React render'siz) va React tashqarisida ishlatishni oson qiladi.
2.14. subscribeWithSelector, combine va computed
subscribeWithSelector — subscribe'ni SELEKTOR bilan (faqat kerakli qism o'zgarsa):
import { subscribeWithSelector } from "zustand/middleware";
const useStore = create(subscribeWithSelector((set) => ({ count: 0, name: "" })));
useStore.subscribe((s) => s.count, (count) => console.log(count)); // FAQAT count o'zgarsa
// oddiy subscribe — butun state; subscribeWithSelector — selektorli subscribe
combine — TypeScript'da turni AVTOMATIK (state + actions ajratib, tur chiqariladi):
import { combine } from "zustand/middleware";
const useStore = create(combine(
{ count: 0 }, // initial state (turi chiqariladi)
(set) => ({ inc: () => set((s) => ({ count: s.count + 1 })) }) // actions
)); // tur qo'lda interface yozmasdan avtomatik
COMPUTED/DERIVED — Zustand'da alohida "computed" yo'q, uch usul:
1) Selektor ichida (oddiy): useStore((s) => s.items.length)
2) get() bilan action: total: () => get().items.reduce(...)
3) Selektor + useShallow/memo (qimmat hisob uchun)
subscribeWithSelector — selektorli subscribe (Misol 11 shu middleware'ni talab qiladi)
combine — interface yozmasdan avtomatik tur; computed — selektor/get (alohida API yo'q)subscribeWithSelector, combine va computed — uch qo'shimcha imkoniyat. (1)
subscribeWithSelectormiddleware —subscribeni selektor bilan ishlatishga imkon beradi:useStore.subscribe((s) => s.count, (count) => ...)— birinchi argument qaysi qismni kuzatishni, ikkinchisi o'zgarganda ishga tushadigan callback'ni beradi (faqatcounto'zgarganda chaqiriladi). Oddiysubscribeesa butun state o'zgarishini kuzatadi — selektorli (ikki argumentli) shakl uchun bu middleware shart (Misol 11 shuni talab qiladi). (2)combinemiddleware — TypeScript'da turni avtomatik chiqaradi:combine({ count: 0 }, (set) => ({ inc: ... }))— birinchi argument boshlang'ich state (turi undan chiqariladi), ikkinchisi actions —interfaceqo'lda yozish shart emas (kichik store'lar uchun qulay, katta store'ga aniqcreate<T>afzal). (3) Computed/derived: Zustand'da alohida "computed" API yo'q — hosilangan qiymatni (a) selektor ichida hisoblash (useStore((s) => s.items.length)— oddiy, kichik hisob uchun), (b)get()bilan action ichida (total: () => get().items.reduce(...)), yoki (c) qimmat hisob uchun selektor +useShallow/useMemobilan olasiz. Ikki nuqta: (1)subscribeWithSelector— selektorlisubscribe(bu middleware'sizsubscribefaqat bitta argument oladi); (2)combine— avtomatik tur, computed — selektor/get(Redux'ningcreateSelectorreselect'iga o'xshash alohida API yo'q, lekin oddiy).
2.15. SSR va Next.js — store'ni har so'rovda yaratish
MUAMMO (SSR/Next.js App Router): modul darajasida create(...) — store BUTUN server uchun
bitta (barcha foydalanuvchi so'rovlari bir store'ni bo'lishadi ma'lumot sizishi!).
YECHIM: store'ni FABRIKA funksiyada yaratib, Provider (Context) orqali har so'rovga alohida:
// store.ts — fabrika (har chaqiriqda YANGI vanilla store):
import { createStore } from "zustand/vanilla";
export const createCounterStore = () =>
createStore<CounterStore>((set) => ({ count: 0, inc: () => set((s) => ({ count: s.count + 1 })) }));
// provider.tsx — har so'rovda useRef bilan bir marta yaratish:
const StoreContext = createContext(null);
export function CounterProvider({ children }) {
const storeRef = useRef();
if (!storeRef.current) storeRef.current = createCounterStore(); // bir marta (so'rov/mijoz)
return <StoreContext.Provider value={storeRef.current}>{children}</StoreContext.Provider>;
}
SSR: modul darajasidagi create — server'da BITTA store (so'rovlar aralashadi) — xavfli
Yechim: fabrika + Context + useRef (har so'rov/mijoz uchun alohida store)SSR va Next.js — store'ni har so'rovda yaratish — server-side rendering'ning muhim tuzog'i. Muammo: mijoz (browser) ilovada modul darajasida
create(...)bilan store yaratish xavfsiz (har foydalanuvchining o'z browseri). Lekin SSR/Next.js App Router'da server bitta jarayonda ko'p foydalanuvchi so'rovini xizmat qiladi — modul darajasidagi bitta store barcha so'rovlar o'rtasida bo'lishiladi (bir foydalanuvchi ma'lumoti boshqasiga sizib ketishi mumkin — jiddiy xavf). Yechim: store'ni fabrika funksiya (createCounterStore = () => createStore(...)) orqali yaratib, uni React Context +useRefbilan har so'rov/mijoz uchun alohida taqdim etish (if (!storeRef.current) storeRef.current = createCounterStore()— komponent daraxti bir marta yaratadi). KomponentdauseStore(store, selector)bilan Context'dagi store'ga ulanasiz. Ikki nuqta: (1) SSR'da modul darajasidagicreate— server'da yagona store (so'rovlar aralashadi — ma'lumot sizishi); (2) yechim — fabrika + Context +useRef(har so'rov/mijoz uchun alohida vanilla store,zustand/vanilla). Sof mijoz (CSR) ilovada bu shart emas (oddiycreateyetadi) — faqat SSR/Next.js'da zarur.
2.16. Store'ni test qilish
ZUSTAND STORE TESTI — vanilla API oson (React render shart emas):
import { useCounterStore } from "./store";
// Har testdan oldin store'ni RESET (holat testlar orasida oqib ketmasin):
beforeEach(() => {
useCounterStore.setState({ count: 0 }); // yoki dastlabki holatni tiklash
});
test("increment count'ni oshiradi", () => {
useCounterStore.getState().increment(); // action (hook'siz)
expect(useCounterStore.getState().count).toBe(1); // holatni tekshirish
});
Test — getState()/setState() bilan (React render'siz — vanilla API)
beforeEach'da reset — testlar bir-biriga ta'sir qilmasligi uchun (izolyatsiya)Store'ni test qilish — Zustand'ni test qilish oson, chunki vanilla API (
getState/setState) React render talab qilmaydi 2.13-bob. Store logikasini test qilish uchun:useCounterStore.getState().increment()bilan action'ni chaqirasiz,expect(useCounterStore.getState().count).toBe(1)bilan natijani tekshirasiz — komponent render qilmasdan. Muhim: store modul darajasida yaratilgani uchun holat testlar orasida saqlanadi —beforeEachdasetStatebilan reset qilish shart (aks holda bir test boshqasiga ta'sir qiladi — izolyatsiya buziladi). Komponent bilan birga (integration) test qilishda esa React Testing Library'da (render+fireEvent) oddiy hook sifatida ishlatiladi. Ikki nuqta: (1) test —getState()/setState()bilan (React render'siz — tez, oddiy); (2)beforeEachda reset (testlar izolyatsiyasi — holat oqmasin). Bu — Zustand'ni Redux'dan test qilishda soddaroq qilgan jihat (mock store, Provider o'rash shart emas).
2.17. Zustand vs Jotai vs Valtio — atom va proxy
UCH "yengil" state kutubxonasi (hammasi pmndrs/Poimandres) — turli yondashuv:
┌──────────┬────────────────────────────────────────────────────┐
│ Zustand │ BITTA store (top-down) — selektor bilan o'qish │
│ │ create(() => ({...})); useStore(s => s.x) │
│ │ global store'ga o'rgangan (Redux) uchun tanish │
│ Jotai │ ATOM'lar (bottom-up) — kichik mustaqil bo'laklar │
│ │ atom(0); useAtom(countAtom) — Recoil'ga o'xshash │
│ │ juda granular, atomlar bir-biriga bog'lanadi │
│ Valtio │ PROXY (mutatsiya) — to'g'ridan o'zgartirasiz │
│ │ proxy({count:0}); snap.count; state.count++ │
│ │ mutatsiya sintaksisi (immutable haqida o'ylamaslik)│
└──────────┴────────────────────────────────────────────────────┘
Zustand — bitta store + selektor (eng ko'p ishlatiladigan, universal)
Jotai — atom (granular, derived-og'ir UI); Valtio — proxy (mutatsiya yoqtiruvchilarga)Zustand vs Jotai vs Valtio — atom va proxy — uchalasi ham bir jamoa (pmndrs/Poimandres) tomonidan yaratilgan yengil state kutubxonalari, lekin yondashuvi har xil. (1) Zustand — bitta markaziy store (top-down):
createbilan store yaratib,useStore(s => s.x)selektor bilan o'qiysiz — Redux'dan kelganlar uchun eng tanish, eng universal, eng ko'p ishlatiladigan. (2) Jotai — atom'lar (bottom-up, Recoil'ga o'xshash): state kichik mustaqil bo'laklar (const countAtom = atom(0)) sifatida,useAtom(countAtom)bilan ishlatiladi; atomlar bir-biridan derived bo'ladi (atom((get) => get(countAtom) * 2)) — juda granular, ko'p bir-biriga bog'liq hosilangan qiymatlar (derived-og'ir UI) uchun qulay. (3) Valtio — proxy asosida (mutatsiya sintaksisi):const state = proxy({ count: 0 })yaratib,state.count++bilan to'g'ridan mutatsiya qilasiz (Valtio proxy orqali kuzatadi), komponentdauseSnapshot(state)bilan o'qiysiz — immutable haqida o'ylashni yoqtirmaganlar uchun. Ikki nuqta: (1) Zustand — bitta store + selektor (universal, eng keng — shubha bo'lsa Zustand); (2) Jotai — atom (granular, derived-og'ir), Valtio — proxy (mutatsiya sintaksisi). Uchalasi ham yaxshi; Zustand — eng keng qamrovli va standart tanlov, Jotai/Valtio — muayyan uslubga (atom yoki mutatsiya) mos kelganda.
3. Sintaksis — tez ma'lumotnoma
CREATE 2.2-bob: const useStore = create((set, get) => ({ state, actions }))
SET 2.3-bob: set((s) => ({ count: s.count + 1 })) | set({ filter: "all" })
GET 2.3-bob: const { items } = get() // action ichida o'qish
SELEKTOR 2.4-bob: const count = useStore((s) => s.count) // faqat count
SHALLOW 2.5-bob: useStore(useShallow((s) => ({ a: s.a, b: s.b }))) // ko'p qism
PERSIST 2.6-bob: create(persist((set) => ({...}), { name: "store" }))
DEVTOOLS 2.6-bob: create(devtools((set) => ({...})))
IMMER 2.6-bob: create(immer((set) => ({ add: (x) => set((s) => { s.items.push(x) }) })))
ASYNC 2.7-bob: fetchX: async () => { set({loading:true}); ...; set({data, loading:false}) }
TYPED 2.9-bob: create<StoreType>((set) => ({...}))
SLICES 2.8-bob: create((...a) => ({ ...createUserSlice(...a), ...createCartSlice(...a) }))
REPLACE 2.11-bob: set({ count: 0 }, true) // butun state ALMASHTIRISH (actions ham) — ehtiyot
VANILLA 2.13-bob: import { createStore } from "zustand/vanilla" // React'siz store
OUTSIDE 2.13-bob: useStore.getState() | .setState({...}) | .subscribe(fn) // komponent tashqarisi
TRANSIENT 2.12-bob: useStore.subscribe((s) => { ref.current.style... }) // render'siz (ref)
SUB+SELECT 2.14-bob: create(subscribeWithSelector((set) => ...)) // useStore.subscribe(sel, cb)
COMBINE 2.14-bob: create(combine({ count: 0 }, (set) => ({ inc: ... }))) // avtomatik tur
SSR 2.15-bob: fabrika + Context + useRef // har so'rov uchun alohida vanilla store4. Batafsil kod namunalari
Misol 1 — Asosiy store (counter — 2.2, 2.3)
import { create } from "zustand";
interface CounterStore {
count: number;
increment: () => void;
decrement: () => void;
reset: () => void;
}
export const useCounterStore = create<CounterStore>((set) => ({
count: 0,
increment: () => set((s) => ({ count: s.count + 1 })), // oldingi state'dan (2.3)
decrement: () => set((s) => ({ count: s.count - 1 })),
reset: () => set({ count: 0 }), // to'g'ridan qiymat
}));
// Ishlatish — Provider YO'Q:
function Counter() {
const count = useCounterStore((s) => s.count); // selektor (2.4)
const { increment, decrement } = useCounterStore(); // (oddiy holatda — action'lar barqaror)
return (
<div>
<button onClick={decrement}>-</button>
<span>{count}</span>
<button onClick={increment}>+</button>
</div>
);
}Misol 2 — Selektor (re-render — 2.4)
// Alohida selektorlar (faqat o'sha qism o'zgarganda render):
function Count() {
const count = useStore((s) => s.count); // faqat count
return <span>{count}</span>;
}
function Filter() {
const filter = useStore((s) => s.filter); // faqat filter
return <span>{filter}</span>;
}
// count o'zgarsa faqat Count render; filter o'zgarsa faqat Filter (kerakli — 2.4)
// Butun store (har o'zgarishda render):
// const store = useStore(); // count YOKI filter o'zgarsa — render (yomon)Misol 3 — useShallow (ko'p qism — 2.5)
import { useShallow } from "zustand/react/shallow";
function UserCard() {
// Ko'p qism — useShallow (har render yangi obyekt'dan saqlaydi):
const { name, email } = useStore(useShallow((s) => ({ name: s.user.name, email: s.user.email })));
return <div>{name} — {email}</div>;
// name/email o'zgarmasa — render BO'LMAYDI (useShallow yuzaki taqqoslaydi — 2.5)
}Misol 4 — Cart store (Immer middleware — 2.6)
import { create } from "zustand";
import { immer } from "zustand/middleware/immer";
interface CartStore {
items: CartItem[];
addItem: (item: CartItem) => void;
removeItem: (id: string) => void;
updateQty: (id: string, qty: number) => void;
total: () => number;
}
export const useCartStore = create<CartStore>()(
immer((set, get) => ({
items: [],
addItem: (item) => set((s) => {
const existing = s.items.find((i) => i.id === item.id);
if (existing) existing.qty += 1; // Immer — mutatsiya OK (2.6)
else s.items.push({ ...item, qty: 1 });
}),
removeItem: (id) => set((s) => { s.items = s.items.filter((i) => i.id !== id); }),
updateQty: (id, qty) => set((s) => {
const item = s.items.find((i) => i.id === id);
if (item) item.qty = qty;
}),
total: () => get().items.reduce((sum, i) => sum + i.price * i.qty, 0), // get bilan (2.3)
}))
);Misol 5 — Persist (localStorage — 2.6)
import { create } from "zustand";
import { persist } from "zustand/middleware";
interface ThemeStore {
theme: "light" | "dark";
toggle: () => void;
}
export const useThemeStore = create<ThemeStore>()(
persist(
(set) => ({
theme: "light",
toggle: () => set((s) => ({ theme: s.theme === "light" ? "dark" : "light" })),
}),
{ name: "theme-storage" } // localStorage kaliti (2.6)
)
);
// theme localStorage'da saqlanadi (sahifa yangilansa qoladi — 2.6)Misol 6 — Auth store (persist + async — 2.6, 2.7)
interface AuthStore {
user: User | null;
token: string | null;
isLoading: boolean;
login: (email: string, password: string) => Promise<void>;
logout: () => void;
}
export const useAuthStore = create<AuthStore>()(
persist(
(set) => ({
user: null,
token: null,
isLoading: false,
login: async (email, password) => {
set({ isLoading: true });
try {
const { data } = await api.post("/auth/login", { email, password });
set({ user: data.user, token: data.token, isLoading: false });
} catch (err) {
set({ isLoading: false });
throw err; // komponentga xato
}
},
logout: () => set({ user: null, token: null }),
}),
{ name: "auth", partialize: (s) => ({ token: s.token }) } // faqat token saqla (2.6)
)
);
// partialize — faqat ba'zi maydonni saqlash (token — user emas, qayta tiklanadi)Misol 7 — DevTools middleware (2.6)
import { create } from "zustand";
import { devtools } from "zustand/middleware";
export const useStore = create<Store>()(
devtools(
(set) => ({
count: 0,
increment: () => set((s) => ({ count: s.count + 1 }), false, "increment"), // action nomi
}),
{ name: "MyAppStore" }
)
);
// Redux DevTools'da Zustand store ko'rinadi (vaqt sayohati — 12.2: 2.8)
// set(..., false, "increment") — action nomini DevTools'ga (kuzatish oson)Misol 8 — Slices pattern (modullik — 2.8)
import { create, StateCreator } from "zustand";
interface UserSlice { user: User | null; setUser: (u: User) => void; }
interface CartSlice { items: CartItem[]; addItem: (i: CartItem) => void; }
type Store = UserSlice & CartSlice;
const createUserSlice: StateCreator<Store, [], [], UserSlice> = (set) => ({
user: null,
setUser: (user) => set({ user }),
});
const createCartSlice: StateCreator<Store, [], [], CartSlice> = (set) => ({
items: [],
addItem: (item) => set((s) => ({ items: [...s.items, item] })),
});
export const useStore = create<Store>()((...args) => ({
...createUserSlice(...args),
...createCartSlice(...args),
}));
// Har slice alohida (modullik — katta store tartibli — 2.8)Misol 9 — Store action'larini birga olish (2.4)
// Actions barqaror (o'zgarmaydi) — birga olish mumkin (yoki alohida selektor):
function CartControls() {
const { addItem, removeItem, clearCart } = useCartStore(
useShallow((s) => ({ addItem: s.addItem, removeItem: s.removeItem, clearCart: s.clearCart }))
);
// yoki alohida: const addItem = useCartStore((s) => s.addItem);
return <button onClick={clearCart}>Tozalash</button>;
}
// Action'lar barqaror — alohida selektor afzal (yoki useShallow — 2.5)Misol 10 — Store'dan tashqarida o'qish/yozish (2.3)
// Komponent tashqarida store'ni ishlatish (getState/setState):
const token = useAuthStore.getState().token; // joriy qiymatni o'qish (hook'siz)
useAuthStore.getState().logout(); // action chaqirish (hook'siz)
useAuthStore.setState({ user: null }); // to'g'ridan yangilash
// Masalan axios interceptor'da (komponent emas):
api.interceptors.request.use((config) => {
const token = useAuthStore.getState().token; // store'dan token (11.15)
if (token) config.headers.Authorization = `Bearer ${token}`;
return config;
});
// getState/setState — komponent tashqarida (interceptor, util) — hook'siz (2.3)Misol 11 — Subscribe (o'zgarishni kuzatish — 2.14)
import { create } from "zustand";
import { subscribeWithSelector } from "zustand/middleware";
// Selektorli subscribe uchun subscribeWithSelector middleware SHART 2.14-bob:
const useStore = create<Store>()(
subscribeWithSelector((set) => ({ count: 0, /* ... */ }))
);
// Store o'zgarishini komponent tashqarida kuzatish (selektorli — faqat count):
const unsubscribe = useStore.subscribe(
(state) => state.count, // qaysi qism (selektor)
(count) => console.log("count o'zgardi:", count) // o'zgarganda callback
);
// unsubscribe(); // kuzatishni to'xtatish
// Middleware'siz oddiy subscribe — butun state (bitta argument):
// useStore.subscribe((state) => console.log(state.count));
// subscribe — store o'zgarishiga komponent tashqarida reaksiya (analytics, log, transient — 2.12)
// Selektorli (ikki argumentli) subscribe uchun subscribeWithSelector kerak (2.14)Misol 12 — Modal/UI store (client state — 2.7)
// Global UI holati (modal, sidebar, toast) — Zustand'ning ideal ishlatilishi:
interface UIStore {
isSidebarOpen: boolean;
modal: React.ReactNode | null;
toggleSidebar: () => void;
openModal: (content: React.ReactNode) => void;
closeModal: () => void;
}
export const useUIStore = create<UIStore>((set) => ({
isSidebarOpen: false,
modal: null,
toggleSidebar: () => set((s) => ({ isSidebarOpen: !s.isSidebarOpen })),
openModal: (content) => set({ modal: content }),
closeModal: () => set({ modal: null }),
}));
// Ishlatish: const openModal = useUIStore((s) => s.openModal); openModal(<Form />);
// UI holati (modal/sidebar) — client state Zustand (server emas — 2.10)Misol 13 — Zustand + TanStack Query (zamonaviy kombinatsiya — 2.10)
// SERVER state — TanStack Query 12.4-bob; CLIENT state — Zustand:
function ProductsPage() {
// Server ma'lumoti — TanStack Query (kesh/sync):
const { data: products } = useQuery({ queryKey: ["products"], queryFn: fetchProducts });
// Client UI holati — Zustand (filter, ko'rinish):
const view = useUIStore((s) => s.productView); // "grid" | "list"
const setView = useUIStore((s) => s.setProductView);
return (
<div>
<button onClick={() => setView(view === "grid" ? "list" : "grid")}>Ko'rinish</button>
<ProductList products={products} view={view} /> {/* server data + client view */}
</div>
);
}
// Server (Query) + client (Zustand) — zamonaviy stack (Redux'siz — 2.10)Misol 14 — Computed/derived (selektor bilan — 2.4)
// Hosilangan qiymat — selektor ichida hisoblash (Redux createSelector'dek emas, oddiy):
function CartSummary() {
// Selektor ichida hisoblash (har render — kichik massiv uchun OK):
const total = useCartStore((s) => s.items.reduce((sum, i) => sum + i.price * i.qty, 0));
const count = useCartStore((s) => s.items.length);
// Katta/qimmat hisob store ichida memoize yoki get() bilan action (2.3)
return <div>Jami: {total} so'm ({count} ta)</div>;
}
// Hosilangan — selektor ichida (oddiy); qimmat store action (get) yoki memoMisol 15 — Transient update (render'siz — 2.12)
import { useRef, useEffect } from "react";
// Tez-tez o'zgaradigan qiymat (mouse) — subscribe + ref (render YO'Q — 2.12):
function CursorTrail() {
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
// Store o'zgarishini React render'sidan TASHQARIDA kuzatish:
const unsub = useMouseStore.subscribe((s) => {
if (ref.current) {
ref.current.style.transform = `translate(${s.x}px, ${s.y}px)`; // to'g'ridan DOM
}
});
return unsub; // cleanup — kuzatishni to'xtatish
}, []);
return <div ref={ref} className="cursor" />;
// x/y sekundiga o'nlab marta o'zgarsa ham komponent RENDER BO'LMAYDI (2.12)
}Misol 16 — Vanilla store va test (React'siz — 2.13, 2.16)
import { createStore } from "zustand/vanilla";
// React'siz store (Node, test, util):
export const counterStore = createStore<{ count: number; inc: () => void }>((set) => ({
count: 0,
inc: () => set((s) => ({ count: s.count + 1 })),
}));
// --- test (React render shart emas — vanilla API — 2.16) ---
beforeEach(() => counterStore.setState({ count: 0 })); // izolyatsiya (reset)
test("inc count'ni oshiradi", () => {
counterStore.getState().inc();
expect(counterStore.getState().count).toBe(1);
});
// React komponentida ishlatish uchun:
// import { useStore } from "zustand";
// const count = useStore(counterStore, (s) => s.count);Misol 17 — SSR/Next.js — fabrika + Provider (2.15)
// store.ts — fabrika (har so'rov/mijoz uchun YANGI store — server'da izolyatsiya):
import { createStore } from "zustand/vanilla";
type CounterStore = { count: number; inc: () => void };
export const createCounterStore = () =>
createStore<CounterStore>((set) => ({ count: 0, inc: () => set((s) => ({ count: s.count + 1 })) }));
// provider.tsx — Context + useRef (bir marta yaratish):
import { createContext, useContext, useRef } from "react";
import { useStore } from "zustand";
const StoreContext = createContext<ReturnType<typeof createCounterStore> | null>(null);
export function CounterProvider({ children }: { children: React.ReactNode }) {
const storeRef = useRef<ReturnType<typeof createCounterStore>>();
if (!storeRef.current) storeRef.current = createCounterStore(); // so'rov/mijoz bo'yicha bir marta
return <StoreContext.Provider value={storeRef.current}>{children}</StoreContext.Provider>;
}
// Custom hook — Context'dagi store'ga selektor bilan ulanish:
export function useCounter<T>(selector: (s: CounterStore) => T): T {
const store = useContext(StoreContext);
if (!store) throw new Error("useCounter — CounterProvider ichida bo'lishi kerak");
return useStore(store, selector);
}
// SSR'da modul darajasidagi create — server'da bitta store (so'rovlar aralashadi) — xavfli (2.15)5. To'g'ri va noto'g'ri holatlar
1) Selektor
const store = useStore() (butun store — har o'zgarishda render — 2.4)
const count = useStore((s) => s.count) (faqat kerakli qism)2) Ko'p qism
useStore((s) => ({ a: s.a, b: s.b })) (har render yangi obyekt — render — 2.5)
useShallow yoki alohida selektorlar3) Server ma'lumoti
Zustand async action'da API ma'lumotini saqlash (kesh/sync yo'q — 2.7)
server TanStack Query; client Zustand (2.10)4) State saqlash
qo'lda localStorage (har o'zgarishda sync)
persist middleware (avtomatik — 2.6)5) Vosita tanlovi
kichik UI holatiga Redux (boilerplate)
Zustand (yengil — Provider/boilerplate yo'q — 2.1, 2.10)6) Action set
to'g'ridan state mutatsiya (Immer'siz — buziladi)
set((s) => ({...})) yoki immer middleware (2.3, 2.6)7) Ichma-ich yangilash
set({ user: { name } }) (user.email va boshqa maydonlar yo'qoladi — merge 1-daraja — 2.11)
set((s) => ({ user: { ...s.user, name } })) yoki immer middleware8) SSR/Next.js store
modul darajasida create(...) (server'da bitta store — so'rovlar aralashadi — 2.15)
fabrika + Context + useRef (har so'rov/mijoz uchun alohida store — Misol 17)9) Selektorli subscribe
useStore.subscribe((s) => s.x, cb) (subscribeWithSelector'siz ishlamaydi — 2.14)
create(subscribeWithSelector((set) => ...)) selektorli subscribe (Misol 11)6. Keng tarqalgan xatolar va yechimlari
Xato 1 — Komponent har render re-render (sekin)
Sababi: butun store (selektorsiz) yoki obyekt qaytaradigan selektor (2.4, 2.5). Yechimi: alohida selektor (s => s.x); ko'p qism — useShallow.
Xato 2 — State o'zgardi, lekin komponent yangilanmaydi
Sababi: setda yangi havola yo'q (mutatsiya — Immer'siz). Yechimi: set((s) => ({...})) (yangi obyekt) yoki immer middleware (Misol 4).
Xato 3 — persist ishlamaydi (yangilansa yo'qoladi)
Sababi: persist middleware yo'q yoki name yo'q 2.6-bob. Yechimi: persist((set) => ({...}), { name: "store" }) (Misol 5).
Xato 4 — Server ma'lumoti eskirgan (Zustand'da)
Sababi: server ma'lumoti Zustand'da, sync yo'q 2.7-bob. Yechimi: TanStack Query (server state — 2.10).
Xato 5 — useShallow topilmaydi
Sababi: eski versiya yoki noto'g'ri import. Yechimi: import { useShallow } from "zustand/react/shallow" (v4.4+).
Xato 6 — Action'da eski state
Sababi: get() ishlatilmagan, eski closure. Yechimi: action ichida get() bilan joriy state o'qi (Misol 4).
Xato 7 — TypeScript create xatosi
Sababi: create<T>() (qo'sh qavs) middleware bilan. Yechimi: create<T>()(middleware(...)) (qo'sh qavs — middleware bilan — Misol 4, 5).
Xato 8 — Ichma-ich maydon yo'qoladi
Sababi: set({ user: { name } }) — merge faqat 1-daraja, butun user almashtiriladi 2.11-bob. Yechimi: set((s) => ({ user: { ...s.user, name } })) yoki immer middleware.
Xato 9 — SSR'da ma'lumot boshqa foydalanuvchiga sizadi
Sababi: modul darajasidagi create — server'da barcha so'rovlar bir store'ni bo'lishadi 2.15-bob. Yechimi: fabrika + Context + useRef (har so'rov uchun alohida store — Misol 17).
Xato 10 — Selektorli subscribe callback chaqirilmaydi
Sababi: subscribeWithSelector middleware ishlatilmagan (ikki argumentli subscribe ishlamaydi — 2.14). Yechimi: create(subscribeWithSelector((set) => ...)) (Misol 11).
7. Integratsiya — bu mavzu stack'ning qayerida uchraydi
- Context 12.1-bob: Zustand — Context'ning selektorli, kuchliroq muqobili.
- Redux Toolkit 12.2-bob: Zustand — Redux'ning yengil alternativasi (boilerplate'siz).
- TanStack Query 12.4-bob: server (Query) + client (Zustand) — zamonaviy stack.
- useLocalStorage 11.7-bob: persist — store darajasida localStorage.
- Auth 11.15-bob: auth store + getState (interceptor — Misol 10).
- TypeScript 11.14-bob: create
— type-safe store. - Performance 11.11-bob: selektor va transient update — re-render optimizatsiyasi (2.4, 2.12).
- Immer 12.2-bob: immer middleware — mutatsiya sintaksisi.
- Testing (11.x): vanilla API (getState/setState) — React render'siz store testi 2.16-bob.
- SSR/Next.js (13-QISM): fabrika + Context — har so'rovda alohida store 2.15-bob.
- Jotai/Valtio: bir oiladagi muqobil yengil kutubxonalar (atom/proxy — 2.17).
8. Eng yaxshi amaliyotlar (best practices)
- Doim selektor (
s => s.x— butun store'ni olmang — 2.4). - Ko'p qism useShallow (obyekt selektor — referential — 2.5).
- Server ma'lumotini Zustand'da saqlamang (TanStack Query — client state — 2.7, 2.10).
- persist saqlanishi kerak state'ga (theme/auth — partialize — 2.6).
- immer ichma-ich state'ga (mutatsiya sintaksisi — 2.6).
- devtools development'da (kuzatish — 2.6).
- Slices katta store'ga (modullik — 2.8).
- create
(type-safe — 2.9). - getState/setState komponent tashqarida (interceptor/util — 2.3, 2.13).
- Zustand + TanStack Query (zamonaviy, yengil stack — 2.10).
- Ichma-ich state'ga spread yoki immer (merge faqat 1-daraja — 2.11).
- Transient update tez-tez o'zgaradigan qiymatga (subscribe + ref — render'siz — 2.12).
- SSR/Next.js'da fabrika + Context (modul create'dan qoching — 2.15).
- Store'ni vanilla API bilan test qiling (getState/setState + beforeEach reset — 2.16).
9. Amaliy loyiha: "Zustand bilan Client State"
Zustand'ni real ilovada — UI, auth, cart — mustahkamlash.
Maqsad
Ilova uchun Zustand store'lar yaratish: auth (persist), cart (immer), UI (modal/theme) — selektor, middleware bilan.
Talablar (requirements)
- Counter/oddiy store: create + set/get (Misol 1).
- Selektor: doim selektor bilan; ko'p qism — useShallow (Misol 2, 3).
- Cart store: immer middleware, add/remove/updateQty/total (Misol 4).
- Auth store: persist (partialize token) + async login (Misol 6).
- Theme store: persist (localStorage — Misol 5).
- UI store: modal/sidebar (client state — Misol 12).
- devtools: kamida bitta store DevTools bilan (Misol 7).
- Slices: katta store'ni slice'larga bo'lish (Misol 8, 2.8).
- getState: interceptor/util'da store'dan token (Misol 10).
- Zustand + Query: server (Query) + client (Zustand) ajratish (Misol 13).
- Transient: kursor yoki progress — subscribe + ref (render'siz — Misol 15).
- Test: kamida bitta store'ni vanilla API bilan test qilish (Misol 16, 2.16).
Maslahatlar (hint)
- Doim selektor (
s => s.x) — butun store'ni olmang (Xato 1). - Ko'p qism — useShallow (obyekt selektor — Misol 3).
- Cart — immer (mutatsiya sintaksisi — Misol 4).
- Auth — persist + partialize (faqat token — Misol 6).
- Server ma'lumoti — Zustand'da emas, Query'da 2.10-bob.
- create
() (middleware bilan qo'sh qavs — Xato 7). - Transient — subscribe + ref (render'siz — Misol 15).
- Test — vanilla API (getState/setState), beforeEach'da reset (Misol 16).
"Tayyor" mezonlari (acceptance criteria)
- Bir nechta store (auth/cart/ui/theme).
- Selektor bilan (re-render optimal).
- Cart immer bilan.
- Auth persist + async.
- Theme persist.
- UI modal/sidebar.
- devtools (kamida bitta).
- Slices (katta store).
- getState (interceptor).
- Server (Query) + client (Zustand) ajratilgan.
- Transient update (subscribe + ref — render'siz).
- Kamida bitta store vanilla API bilan test qilingan.
Yechim kodi ataylab berilmagan — bu loyihani o'zingiz yozib ko'ring.
10. Xulosa va keyingi bobga ko'prik
Bu bobda Zustand'ni chuqur o'rgandik:
- Nega Zustand (Redux/Context o'rtasi — 2.1); create 2.2-bob; state/actions/set/get 2.3-bob; selektor (re-render — 2.4); useShallow 2.5-bob.
- Middleware (persist/devtools/immer — 2.6); async 2.7-bob; slices 2.8-bob; TypeScript 2.9-bob.
- Zustand vs Redux vs Context (yakuniy qaror — 2.10); set chuqurroq (merge/replace/get — 2.11).
- Transient update (subscribe + ref, render'siz — 2.12); vanilla store (React'siz — 2.13); subscribeWithSelector/combine/computed 2.14-bob.
- SSR/Next.js (fabrika + Context — 2.15); test (vanilla API — 2.16); Zustand vs Jotai vs Valtio (atom/proxy — 2.17).
Endi siz client state'ni Zustand bilan yengil, kam kod, lekin kuchli (selektor, persist, middleware) tarzda boshqara olasiz. Eng muhimi — zamonaviy stackni (TanStack Query + Zustand) va har state turiga (client/server, kichik/katta) mos vositani tanlashni bilib oldingiz.
Keyingi bob — 12.6-bob: UI kutubxonalar — shadcn/ui, MUI, Ant Design. State management'ni tugatdik; endi UI'ning o'ziga qaytamiz. Har komponentni (tugma, modal, jadval, dropdown, sana tanlagich) noldan yozish — sekin va xatoga moyil. UI kutubxonalar tayyor, sinalgan, a11y'li komponentlar beradi. Uch xil yondashuvni ko'ramiz: MUI (Material UI — to'liq stillangan), Ant Design (enterprise — ko'p komponent), va shadcn/ui (copy-paste — Radix + Tailwind — eng zamonaviy). Bu — UI qurish tezligini keskin oshiradi va professional, a11y'li natija beradi.
Foydalanilgan rasmiy/ishonchli manbalar
- Zustand rasmiy hujjati —
create, selektorlar,useShallow,getState/setState/subscribe, async actions - Zustand hujjati — middleware bo'limi:
persist(name,partialize,storage),devtools,immer,subscribeWithSelector,combine - Zustand hujjati — slices pattern (
StateCreator), TypeScript (create<T>(), middleware tiplash), transient updates - Zustand hujjati — vanilla store (
zustand/vanilla,createStore), Next.js/SSR (fabrika + Provider), testing (setup) - pmndrs (Poimandres) ekotizimi — Zustand, Jotai (atom-based), Valtio (proxy-based) qiyoslash
- TkDodo blogi — "Working with Zustand" (best practices, selektorlar, store tuzilishi)
- React rasmiy hujjati — client vs server state; TanStack Query bilan zamonaviy stack
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!