Zustand — o'rnatish, store yaratish, ishlatish
Muammodan boshlaylik
56-darsda savatni global state uchun nomzod deb topdik. Context bilan yozsak:
createContext, Provider komponenti,useContexthook i- Provider ni
Appga 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
setni 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 uchunuseShallowyoki 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 —
useShallowyoki alohida chaqiruvlar get()— action ichida joriy stateuseStore.getState(),setState(),subscribe()— React dan tashqarida- Bir nechta kichik store — odatiy amaliyot
Keyingi dars
58-dars: Zustand — async action, middleware, devtools