Zustand — o'rnatish, store yaratish, ishlatish

~20 daq · React · State Boshqarish

Muammodan boshlaylik

56-darsda savatni global state uchun nomzod deb topdik. Context bilan yozsak:

  • createContext, Provider komponenti, useContext hook i
  • Provider ni App ga o'rash
  • Va asosiy muammo: savatdagi istalgan o'zgarish — Context ni ishlatadigan barcha komponentlarni qayta render qiladi

Zustand (nemischa "holat") — minimal global state kutubxonasi: Provider siz, selektorlar bilan, bir necha qator kodda.


O'rnatish

npm install zustand

Hajmi ~1 KB. Hech qanday Provider yoki sozlash kerak emas.


Birinchi store

// store/savatStore.js
import { create } from 'zustand';

export const useSavatStore = create((set) => ({
  // state
  mahsulotlar: [],

  // action lar
  qoshish: (mahsulot) =>
    set((state) => ({ mahsulotlar: [...state.mahsulotlar, mahsulot] })),

  ochirish: (id) =>
    set((state) => ({
      mahsulotlar: state.mahsulotlar.filter((m) => m.id !== id),
    })),

  tozalash: () => set({ mahsulotlar: [] }),
}));
  • create — store yaratadi va hook qaytaradi (useSavatStore)
  • Funksiya set ni oladi va boshlang'ich state + action larni qaytaradi
  • State va uni o'zgartiradigan funksiyalar — bitta obyektda

set qanday ishlaydi

set({ mahsulotlar: [] });                                   // obyekt — birlashtiriladi

set((state) => ({ soni: state.soni + 1 }));                 // funksiya — oldingi state dan

set yangi qiymatni mavjud state bilan birlashtiradi (shallow merge): set({ mahsulotlar: [] }) boshqa maydonlarga tegmaydi. useState dan farqli — butun obyektni ...state bilan nusxalash shart emas (lekin ichki massiv va obyektlarni baribir yangi qilish kerak — 9-dars).


Komponentda ishlatish

// components/SavatBelgisi.jsx
import { useSavatStore } from '../store/savatStore';

function SavatBelgisi() {
  const soni = useSavatStore((state) => state.mahsulotlar.length);

  return <span>🛒 {soni}</span>;
}
// components/MahsulotKartochka.jsx
function MahsulotKartochka({ mahsulot }) {
  const qoshish = useSavatStore((state) => state.qoshish);

  return (
    <article>
      <h3>{mahsulot.nomi}</h3>
      <button onClick={() => qoshish(mahsulot)}>Savatga</button>
    </article>
  );
}

Provider yo'q, prop drilling yo'q. Har qanday komponent hook ni chaqiradi — tamom.


Selektorlar — eng muhim tushuncha

const soni = useSavatStore((state) => state.mahsulotlar.length);

(state) => state.mahsulotlar.length — selektor: store dan faqat kerakli qismni oladi. Komponent faqat shu qiymat o'zgarganda qayta render bo'ladi.

// ❌ Butun store — HAR QANDAY o'zgarishda render
const { mahsulotlar, qoshish } = useSavatStore();

// ✅ Faqat kerakli qism
const qoshish = useSavatStore((state) => state.qoshish);

MahsulotKartochka faqat qoshish funksiyasini oladi — u hech qachon o'zgarmaydi. Natijada savatga 100 ta mahsulot qo'shilsa ham, kartochkalar bir marta ham qayta render bo'lmaydi. Context bilan esa — har safar.

Bir nechta qiymat — useShallow

import { useShallow } from 'zustand/react/shallow';

// ❌ har renderda yangi obyekt → cheksiz render yoki ortiqcha renderlar
const { mahsulotlar, tozalash } = useSavatStore((state) => ({
  mahsulotlar: state.mahsulotlar,
  tozalash: state.tozalash,
}));

// ✅ useShallow — obyekt ichidagi qiymatlarni solishtiradi
const { mahsulotlar, tozalash } = useSavatStore(
  useShallow((state) => ({
    mahsulotlar: state.mahsulotlar,
    tozalash: state.tozalash,
  }))
);

// ✅ yoki alohida-alohida
const mahsulotlar = useSavatStore((state) => state.mahsulotlar);
const tozalash = useSavatStore((state) => state.tozalash);

[!WARNING] Selektor har chaqirilganda yangi obyekt yoki massiv qaytarsa (({ ... }), .filter(), .map()), Zustand uni "o'zgardi" deb hisoblaydi. Zustand v5 da bu "Maximum update depth exceeded" xatosiga olib kelishi mumkin. Bir nechta qiymat olish uchun useShallow yoki alohida chaqiruvlar ishlating.


get — action ichida state ni o'qish

export const useSavatStore = create((set, get) => ({
  mahsulotlar: [],

  qoshish: (mahsulot) => {
    const bor = get().mahsulotlar.find((m) => m.id === mahsulot.id);
    if (bor) {
      set((state) => ({
        mahsulotlar: state.mahsulotlar.map((m) =>
          m.id === mahsulot.id ? { ...m, soni: m.soni + 1 } : m
        ),
      }));
    } else {
      set((state) => ({
        mahsulotlar: [...state.mahsulotlar, { ...mahsulot, soni: 1 }],
      }));
    }
  },

  jamiNarx: () =>
    get().mahsulotlar.reduce((jami, m) => jami + m.narx * m.soni, 0),
}));

get() — joriy state ni qaytaradi. Action ichida shartli mantiq uchun.

Hisoblangan qiymatlar

// Komponentda — selektor ichida hisoblash
const jami = useSavatStore((state) =>
  state.mahsulotlar.reduce((s, m) => s + m.narx * m.soni, 0)
);

Selektor son qaytaradi — son o'zgarmasa, render ham yo'q. Hisoblangan qiymatlarni state da alohida saqlamang — ular bilan asosiy ma'lumot nomuvofiq bo'lib qolishi mumkin.


Komponentdan tashqarida

Zustand store — oddiy JavaScript obyekti. Uni React dan tashqarida ham ishlatish mumkin:

// Istalgan joyda — util funksiya, Axios interceptor, test
const mahsulotlar = useSavatStore.getState().mahsulotlar;
useSavatStore.getState().tozalash();
useSavatStore.setState({ mahsulotlar: [] });

// O'zgarishlarga obuna
const unsubscribe = useSavatStore.subscribe((state) => {
  console.log('Savat o\'zgardi:', state.mahsulotlar.length);
});

Bu Context bilan mumkin emas. Masalan, Axios interceptor da (85-dars) auth token ni olish uchun juda qulay.


Bir nechta store

store/
├── savatStore.js       // savat
├── sozlamalarStore.js  // mavzu, til
└── authStore.js        // token, foydalanuvchi

Zustand da bitta katta store o'rniga bir nechta kichik store yaratish odatiy hol. Har biri o'z vazifasiga javob beradi, va bir-biriga bog'liq emas.


To'liq misol

// store/savatStore.js
import { create } from 'zustand';

export const useSavatStore = create((set) => ({
  mahsulotlar: [],
  qoshish: (m) =>
    set((s) => {
      const bor = s.mahsulotlar.find((x) => x.id === m.id);
      return {
        mahsulotlar: bor
          ? s.mahsulotlar.map((x) => (x.id === m.id ? { ...x, soni: x.soni + 1 } : x))
          : [...s.mahsulotlar, { ...m, soni: 1 }],
      };
    }),
  ochirish: (id) => set((s) => ({ mahsulotlar: s.mahsulotlar.filter((x) => x.id !== id) })),
  tozalash: () => set({ mahsulotlar: [] }),
}));
// pages/SavatSahifasi.jsx
function SavatSahifasi() {
  const mahsulotlar = useSavatStore((s) => s.mahsulotlar);
  const ochirish = useSavatStore((s) => s.ochirish);
  const tozalash = useSavatStore((s) => s.tozalash);
  const jami = mahsulotlar.reduce((s, m) => s + m.narx * m.soni, 0);

  if (mahsulotlar.length === 0) return <p>Savat bo'sh</p>;

  return (
    <section>
      <ul>
        {mahsulotlar.map((m) => (
          <li key={m.id}>
            {m.nomi} × {m.soni} — {(m.narx * m.soni).toLocaleString()} so'm
            <button onClick={() => ochirish(m.id)}>✕</button>
          </li>
        ))}
      </ul>
      <p>Jami: {jami.toLocaleString()} so'm</p>
      <button onClick={tozalash}>Savatni tozalash</button>
    </section>
  );
}

Nima uchun shunday?

Nima uchun Zustand ga Provider kerak emas, Context ga esa kerak?

Chunki ular state ni turli joyda saqlaydi. Context ning qiymati React daraxtining ichida yashaydi — Provider komponentida — va faqat shu daraxt orqali pastga uzatiladi. Zustand esa state ni React dan tashqarida, oddiy JavaScript modulida saqlaydi. Komponentlar unga "obuna" bo'ladi: store o'zgarganda Zustand har bir obunachining selektorini qayta chaqiradi va natija o'zgargan komponentlarnigina render qiladi. Bu yondashuv React ning useSyncExternalStore API siga asoslangan — aynan tashqi store lar uchun yaratilgan rasmiy mexanizm. Natija: Provider yo'q, selektorlar bor, va store ni React dan tashqarida ham ishlatish mumkin.


Xulosa

Eslab qoling:

  • create((set, get) => ({ ...state, ...actions })) — store va hook bir vaqtda
  • Provider kerak emas — hook ni istalgan komponentda chaqiring
  • set — shallow merge; ichki massiv va obyektlarni yangi qiling
  • Selektorlar: useStore((s) => s.qism) — faqat shu qism o'zgarganda render
  • Butun store ni olmang; bir nechta qiymat — useShallow yoki alohida chaqiruvlar
  • get() — action ichida joriy state
  • useStore.getState(), setState(), subscribe() — React dan tashqarida
  • Bir nechta kichik store — odatiy amaliyot

Keyingi dars

58-dars: Zustand — async action, middleware, devtools