WisarWisar
Dasturlash kitobi/12-QISM — State Management45 daqiqa

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

text
  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

text
  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 })) })) — funksiya set (state'ni yangilash) ni oladi va obyekt qaytaradi: ichida state (count: 0) va actions (incrementset bilan 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 (set bilan o'zgartirish — minimal struktura, Redux'ning slice/reducer/action/dispatch ajratishi yo'q); (2) useStoreto'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

text
  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) => ({...})) — funksiya set (state'ni yangilash) va get (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] }))set bilan). set ikki shaklda ishlatiladi: funksiya (set((s) => ({ count: s.count + 1 })) — oldingi state'dan hisoblash — afzal, xuddi useStatening updater'i — 11.4: 2.6) yoki obyekt (set({ filter }) — to'g'ridan yangi qiymat). Muhim: set state'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)

text
  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 faqat count o'zgarganda re-render bo'ladi; agar filter o'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 (selektorsiz useStore() — har o'zgarishda render — performance muammosi); (2) selektor — Zustand'ning Context'dan eng katta afzalligi (tabiiy, oddiy — Redux'ning useSelector'idek, lekin boilerplate'siz). Bu — Zustand'ni performant va Context'dan yaxshiroq qilgan asosiy sabab.

2.5. Shallow va ko'p qism tanlash

text
  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 })))useShallow qaytargan obyektni yuzaki (shallow) solishtiradi (count/name qiymatlari o'zgarmasa — yangi obyekt bo'lsa ham render qilmaydi). Ikki nuqta: (1) obyekt/massiv qaytaradigan selektor useShallow (aks holda har render render — eng keng Zustand tuzog'i); (2) bitta primitiv qiymat oddiy selektor (useShallow shart emas — primitiv === bilan to'g'ri solishtiriladi). Amalda ko'pincha alohida primitiv selektorlar afzal (oddiy, aniq); ko'p qism kerak bo'lganda useShallow. Bu — Zustand'ni performant ishlatishning zarur bilimi.

2.6. Middleware — persist, devtools, immer

text
  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'ni localStorageda (yoki sessionStorage) saqlash (sahifa yangilansa qoladi — 11.7 useLocalStoragening store darajasidagi versiyasi): persist((set) => ({...}), { name: "counter-storage" })name localStorage 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

text
  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 state

Async 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 }) } }set bilan loading/error/data holatlarini boshqarasiz (Redux'ning createAsyncThunk + extraReducers murakkabligisiz — 12.2: 2.7 — bir oddiy funksiya). Ishlatishda useEffectda 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

text
  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'ning combineReducers/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

text
  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; ... }), va create<CounterStore>((set) => ({...})) — turni generic argument sifatida berasiz (create<T>). Shundan keyin selektorlar avtomatik turlanadi: const count = useCounterStore((s) => s.count)s CounterStore turida (autocomplete: s.count, s.increment...), va count number turida. Slices bilan 2.8-bob — har slice StateCreator turini 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) slicesStateCreator turi 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)

text
  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

text
  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'ling

set — merge, funksional, replace va get chuqurroqsetning ichki xatti-harakati (2.3 davomi, ko'p tuzoq shu yerda). (1) Qisman merge (standart): set({ filter: "done" }) faqat filterni o'zgartiradi, count, items va boshqa hamma maydon saqlanadi — Zustand { ...state, filter } ni avtomatik qiladi. Muhim cheklov: merge faqat birinchi darajada ishlaydi — ichma-ich obyektni (set({ user: { name } })) berilsa, butun user obyekti almashtiriladi (user.email yo'qoladi). Ichma-ich yangilash uchun qo'lda spread (set((s) => ({ user: { ...s.user, name } }))) yoki immer middleware 2.6-bob. (2) Funksional set: set((prev) => ({ count: prev.count + 1 })) — oldingi state'dan hisoblaganda afzal (ketma-ket chaqiriqlarda to'g'ri natija, xuddi useStatening updater'i — 11.4). (3) Replace flag: set(obj, true) — ikkinchi argument true bo'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'ga immer yoki 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

text
  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 selektor

Transient 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. subscribe useEffect ichida 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

text
  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. create React hook qaytaradi, lekin ostidagi mexanizm — vanilla store (getState/setState/subscribe). Sof vanilla store'ni import { createStore } from "zustand/vanilla" bilan yaratasiz — u faqat store.getState(), store.setState(), store.subscribe() beradi (React yo'q — Node.js, test, oddiy JS, yoki boshqa framework'da ishlaydi). Uni React'ga bog'lash uchun useStore(store, (s) => s.count) (import zustanddan). Amaliy jihat: create bilan 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

text
  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) subscribeWithSelector middleware — 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 (faqat count o'zgarganda chaqiriladi). Oddiy subscribe esa butun state o'zgarishini kuzatadi — selektorli (ikki argumentli) shakl uchun bu middleware shart (Misol 11 shuni talab qiladi). (2) combine middleware — TypeScript'da turni avtomatik chiqaradi: combine({ count: 0 }, (set) => ({ inc: ... })) — birinchi argument boshlang'ich state (turi undan chiqariladi), ikkinchisi actions — interface qo'lda yozish shart emas (kichik store'lar uchun qulay, katta store'ga aniq create<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/useMemo bilan olasiz. Ikki nuqta: (1) subscribeWithSelector — selektorli subscribe (bu middleware'siz subscribe faqat bitta argument oladi); (2) combine — avtomatik tur, computed — selektor/get (Redux'ning createSelector reselect'iga o'xshash alohida API yo'q, lekin oddiy).

2.15. SSR va Next.js — store'ni har so'rovda yaratish

text
  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 + useRef bilan har so'rov/mijoz uchun alohida taqdim etish (if (!storeRef.current) storeRef.current = createCounterStore() — komponent daraxti bir marta yaratadi). Komponentda useStore(store, selector) bilan Context'dagi store'ga ulanasiz. Ikki nuqta: (1) SSR'da modul darajasidagi create — 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 (oddiy create yetadi) — faqat SSR/Next.js'da zarur.

2.16. Store'ni test qilish

text
  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 — beforeEachda setState bilan 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

text
  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) Zustandbitta markaziy store (top-down): create bilan store yaratib, useStore(s => s.x) selektor bilan o'qiysiz — Redux'dan kelganlar uchun eng tanish, eng universal, eng ko'p ishlatiladigan. (2) Jotaiatom'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) Valtioproxy asosida (mutatsiya sintaksisi): const state = proxy({ count: 0 }) yaratib, state.count++ bilan to'g'ridan mutatsiya qilasiz (Valtio proxy orqali kuzatadi), komponentda useSnapshot(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

text
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 store

4. Batafsil kod namunalari

Misol 1 — Asosiy store (counter — 2.2, 2.3)

ts
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)

tsx
//  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)

tsx
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)

ts
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)

ts
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)

ts
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)

ts
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)

ts
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)

tsx
// 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)

ts
// 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)

ts
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)

ts
// 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)

tsx
// 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)

tsx
// 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 memo

Misol 15 — Transient update (render'siz — 2.12)

tsx
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)

ts
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)

tsx
// 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

text
 const store = useStore()  (butun store — har o'zgarishda render — 2.4)
 const count = useStore((s) => s.count)  (faqat kerakli qism)

2) Ko'p qism

text
 useStore((s) => ({ a: s.a, b: s.b }))  (har render yangi obyekt — render — 2.5)
 useShallow yoki alohida selektorlar

3) Server ma'lumoti

text
 Zustand async action'da API ma'lumotini saqlash (kesh/sync yo'q — 2.7)
 server  TanStack Query; client  Zustand (2.10)

4) State saqlash

text
 qo'lda localStorage (har o'zgarishda sync)
 persist middleware (avtomatik — 2.6)

5) Vosita tanlovi

text
 kichik UI holatiga Redux (boilerplate)
 Zustand (yengil — Provider/boilerplate yo'q — 2.1, 2.10)

6) Action set

text
 to'g'ridan state mutatsiya (Immer'siz — buziladi)
 set((s) => ({...}))  yoki  immer middleware (2.3, 2.6)

7) Ichma-ich yangilash

text
 set({ user: { name } })  (user.email va boshqa maydonlar yo'qoladi — merge 1-daraja — 2.11)
 set((s) => ({ user: { ...s.user, name } }))  yoki  immer middleware

8) SSR/Next.js store

text
 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

text
 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)

  1. Counter/oddiy store: create + set/get (Misol 1).
  2. Selektor: doim selektor bilan; ko'p qism — useShallow (Misol 2, 3).
  3. Cart store: immer middleware, add/remove/updateQty/total (Misol 4).
  4. Auth store: persist (partialize token) + async login (Misol 6).
  5. Theme store: persist (localStorage — Misol 5).
  6. UI store: modal/sidebar (client state — Misol 12).
  7. devtools: kamida bitta store DevTools bilan (Misol 7).
  8. Slices: katta store'ni slice'larga bo'lish (Misol 8, 2.8).
  9. getState: interceptor/util'da store'dan token (Misol 10).
  10. Zustand + Query: server (Query) + client (Zustand) ajratish (Misol 13).
  11. Transient: kursor yoki progress — subscribe + ref (render'siz — Misol 15).
  12. 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!
12.5-bob: Zustand — Wisar