Mundarija (34)
- Bu darsda
- 1. Nega bu kerak?
- 2. persist — holatni saqlash
- 2.1 Eng oddiy ulash
- 2.2 storage — qayerga saqlash
- 2.3 partialize — faqat keraklisi
- 2.4 version va migrate — eski yozuvni ko'chirish
- 2.5 Boshqa tab — storage hodisasi
- 3. devtools va immer
- 3.1 devtools — har o'zgarish jurnalda
- 3.2 immer — "o'zgartirib" yozish
- 3.3 Middleware tartibi
- 4. Slice naqshi va TypeScript
- 4.1 Store'ni bo'laklarga bo'lish
- 4.2 TypeScript: create<T>()(...)
- 4.3 Slice'ning turi: StateCreator
- 5. SSR tuzoqlari
- 5.1 Birinchi tuzoq: umumiy store
- 5.2 Ikkinchi tuzoq: hydration paytidagi birinchi kadr
- 6. Vazifalar qadami: sozlamalar — store'da, commit (holat 22)
- 6.1 Nima quramiz
- 6.2 To'liq store: ilova-holati.ts
- 6.3 Sozlamalar sahifasi
- 6.4 Ro'yxat sozlamani o'qiydi
- 6.5 Testlar va tekshiruvlar
- 7. Ko'p uchraydigan xatolar
- 8. Mashqlar
- 1-mashq (oson): faqat tema saqlansin
- 2-mashq (o'rta): lang → language
- 3-mashq (qiyin): ikki slice va TypeScript'siz tur
- 4-mashq: Vazifalar qadami
- 9. Real ishda
- Xulosa
- Manbalar
Zustand middleware: persist, devtools, immer, slice'lar va SSR tuzoqlari
Qisqacha: Middleware —
createga beriladigan funksiyani o'rab, store'ga yangi imkoniyat qo'shadigan qatlam. Masalan,persistholatnilocalStoragega saqlaydi:partialize— nimani saqlash,version+migrate— eski yozuvni yangi shaklga ko'chirish.devtools— Redux DevTools'da har o'zgarishni ko'rsatadi,immer—state.a.b = 1deb "o'zgartirib" yozishga imkon beradi. Katta store bo'laklarga (slice) bo'linadi. TypeScript'da —create<T>()(...). Serverda chiziladigan ilovada (Next.js) modul darajasidagi store hamma foydalanuvchiga umumiy bo'lib qoladi — har so'rovga alohida store kerak.
Bu darsda
persistbilan sozlamalarni saqlaysiz:name,storage,partialize,version,migrate.devtoolsvaimmermiddleware'larini ulaysiz va ularni qaysi tartibda o'rash kerakligini bilasiz.- Katta store'ni slice'larga bo'lasiz va TypeScript'da
create<T>()(...)ni nega ikki qavs bilan yozilishini tushunasiz. - SSR'dagi ikki tuzoqni ajratasiz: umumiy store va hydration paytidagi birinchi kadr.
vazifalarda Sozlamalar sahifasini quriysiz va Zustand holatini commit qilasiz (holat 22).
Oldin bilishingiz kerak: Zustand I: kichik global store, localStorage va sessionStorage, Reducer + Immer va holat mashinasi fikrlashi, useSyncExternalStore.
1. Nega bu kerak?
Oldingi darsda «Bahor» savatini Zustand'ga ko'chirdik. Endi Jasur aka yangi talablar bilan keldi:
- "Mehmon «ixcham menyu» ni tanlasa, ertaga kirganda ham ixcham bo'lsin." Store xotirada yashaydi. Sahifa yangilansa — hammasi boshidan.
- Sardor savatga xato tushgan taomni qidiryapti: "Qaysi tugma, qachon, nimani o'zgartirdi?"
console.loglarni har joyga qo'yib chiqish — uzoq ish. - Store kattalashdi: savat, mehmon ma'lumoti, sozlamalar, e'lonlar — 200 qatorli bitta fayl. Har kim o'sha faylga yozadi.
Uchala muammoning yechimi — Zustand'ning middleware lari va slice naqshi. To'rtinchi masala keyinroq chiqadi: sayt Next.js ga ko'chganda (23-qism). U yerda sahifa avval serverda chiziladi va bugungi kodning bir qismi xavfli bo'lib qoladi. Buni ham bugun ko'rib qo'yamiz.
Hayotiy o'xshatish: telefon g'ilofi. Telefon (sizning store funksiyangiz) o'zgarmaydi. Unga g'ilof kiydirasiz — u endi tushsa sinmaydi (persist). Ustidan yana bittasi — karta cho'ntagi bilan (devtools). Har qavat bitta imkoniyat qo'shadi va ichkaridagini o'zgartirmaydi.
2. persist — holatni saqlash
2.1 Eng oddiy ulash
Middleware — store yaratuvchi funksiyani o'rab, unga yangi xatti-harakat qo'shadigan funksiya. Zustand'da u oddiy funksiya chaqiruvi: create(persist(yaratuvchi, sozlama)). persist — zustand/middleware modulida, saytdagi natija oynasida bor.
Natija oynasida localStorage ning o'rinbosari (shim) ishlaydi: misol ichida saqlaydi va o'qiydi, lekin sahifa yangilanganda tozalanadi. Haqiqiy brauzerda yozuv saqlanib qoladi.
import { useEffect } from "react";
import { create } from "zustand";
import { persist } from "zustand/middleware";
const useSettingsStore = create(
persist(
(set) => ({
compact: false,
toggleCompact: () =>
set((state) => ({ compact: !state.compact })),
}),
{ name: "bahor:sozlamalar" },
),
);
export default function App() {
const compact = useSettingsStore((state) => state.compact);
const toggle = useSettingsStore((state) => state.toggleCompact);
useEffect(() => {
console.log(localStorage.getItem("bahor:sozlamalar"));
});
return (
<button onClick={toggle}>
Ixcham menyu{compact ? " ✓" : ""}
</button>
);
}Konsolda:
null
{"state":{"compact":true},"version":0}Ekranda:
Ixcham menyu ✓Birinchi qator — ilova ochilganda: xotirada hali hech narsa yo'q. persist boshlang'ich holatni darhol yozmaydi, faqat birinchi set dan keyin. Ikkinchi qator — bosilgandan keyin. Yozuv shakli doim bir xil: { state, version }. state — saqlangan maydonlar, version — sxema raqami (sukut bo'yicha 0). Funksiyalar (toggleCompact) saqlanmaydi: JSON.stringify ularni tashlab yuboradi (localStorage darsidagi qoida).
Ilova qayta ochilganda persist store yaratilayotgan zahoti localStorage dan o'qiydi va boshlang'ich holatga birlashtiradi. localStorage sinxron, shuning uchun bu birinchi chizishdan oldin bo'ladi — birinchi kadrdayoq to'g'ri sozlama. Bu fakt «SSR tuzoqlari» bo'limida muhim bo'ladi.
2.2 storage — qayerga saqlash
Sukut bo'yicha — localStorage. Boshqasini storage sozlamasi bilan berasiz:
import { createJSONStorage, persist } from "zustand/middleware";
persist(yaratuvchi, {
name: "bahor:savat",
// tab yopilguncha saqlansin (masalan, to'lov oynasidagi savat)
storage: createJSONStorage(() => sessionStorage),
});createJSONStorage — getItem/setItem li har qanday saqlash joyi (storage) ustiga JSON qatlamini qo'yadi. Funksiya (() => sessionStorage) ichida berilishiga e'tibor bering: saqlash joyi faqat kerak bo'lganda olinadi. Serverda sessionStorage yo'q — to'g'ridan-to'g'ri yozilsa, modul yuklanishidayoq ReferenceError bo'lardi. Asinxron saqlash joylari (IndexedDB) ham ulanadi — IndexedDB kutubxonalari darsida ko'ramiz.
2.3 partialize — faqat keraklisi
Store'da e'lon matni ham bor. Uni saqlash kerak emas: ertaga kirgan mehmonga kechagi "Osh qo'shildi" e'loni ko'rinmasin. partialize — holatdan saqlanadigan qismni tanlaydigan funksiya:
import { useEffect } from "react";
import { create } from "zustand";
import { persist } from "zustand/middleware";
const useSettingsStore = create(
persist(
(set) => ({
compact: false,
toast: "",
toggleCompact: () =>
set((state) => ({
compact: !state.compact,
toast: "Sozlama saqlandi",
})),
}),
{
name: "bahor:sozlamalar",
partialize: (state) => ({ compact: state.compact }),
},
),
);
export default function App() {
const toast = useSettingsStore((state) => state.toast);
const toggle = useSettingsStore((state) => state.toggleCompact);
useEffect(() => {
console.log(localStorage.getItem("bahor:sozlamalar"));
}, [toast]);
return (
<>
<button onClick={toggle}>Ixcham menyu</button>
<p>{toast}</p>
</>
);
}Konsolda:
null
{"state":{"compact":true},"version":0}Ekranda:
Ixcham menyu
Sozlama saqlandiEkranda e'lon bor, xotirada — yo'q. partialize selektorga o'xshaydi: holatni oladi, saqlanadigan obyektni qaytaradi. Kanonning ilova-holati.ts ida aynan shunday: sozlamalar saqlanadi, e'lon — yo'q.
Diqqat:
localStoragega hech qachon token, parol yoki shaxsiy ma'lumot qo'ymang. Sahifadagi istalgan skript (masalan, XSS orqali kirgan) uni o'qiy oladi. Bu saqlash joyi — sozlamalar va qoralamalar uchun.
2.4 version va migrate — eski yozuvni ko'chirish
Bir oydan keyin savat shakli o'zgardi. Avval taom nomlari saqlanardi: ["Osh"]. Endi miqdor ham kerak: [{ name: "Osh", qty: 1 }]. Lekin minglab mehmonning brauzerida eski shakl yotibdi. Yangi kod uni o'qisa — item.name — undefined.
Sxema versiyasi — saqlangan ma'lumot shaklining raqami. Shakl o'zgarsa, raqamni oshirasiz va migrate funksiyasini yozasiz: u eski yozuvni va uning versiyasini oladi, yangi shaklni qaytaradi. Quyida mehmonning brauzerida eski yozuv bor deb faraz qilamiz (birinchi qator uni qo'lda yozadi):
// mehmonning brauzerida — bir oy oldingi yozuv (versiya 0)
localStorage.setItem(
"bahor:savat",
JSON.stringify({ state: { items: ["Osh", "Manti"] }, version: 0 }),
);
import { create } from "zustand";
import { persist } from "zustand/middleware";
const useCartStore = create(
persist(() => ({ items: [] }), {
name: "bahor:savat",
version: 1,
migrate: (old, version) => {
console.log("migrate, eski versiya:", version);
if (version === 0) {
return {
items: old.items.map((name) => ({ name, qty: 1 })),
};
}
return old;
},
}),
);
export default function App() {
const items = useCartStore((state) => state.items);
return (
<ul>
{items.map((item) => (
<li key={item.name}>
{item.name} × {item.qty}
</li>
))}
</ul>
);
}
console.log(localStorage.getItem("bahor:savat"));Konsolda:
migrate, eski versiya: 0
{"state":{"items":[{"name":"Osh","qty":1},{"name":"Manti","qty":1}]},"version":1}Ekranda:
Osh × 1
Manti × 1Ketma-ketlik: persist saqlangan version: 0 ni ko'rdi, kodda — 1. Farq bor — migrate chaqirildi. U qaytargan obyekt holatga tushdi va darhol yangi versiya bilan qayta yozildi (konsolning ikkinchi qatori). Keyingi ochilishda migrate chaqirilmaydi — versiyalar teng.
Versiyalar ko'paysa, migrate ichida zinapoya yoziladi: if (version < 1) {…}, keyin if (version < 2) {…}. Shunda 0-versiyali eski mehmon ham, 1-versiyali ham oxirgi shaklga yetadi. IndexedDB darsidagi upgradeneeded va oldVersion g'oyasining o'zi.
Diqqat:
versionni oshirib,migrateni yozmasangiz, Zustand konsolga xato yozadi:State loaded from storage couldn't be migrated since no migrate function was provided. Tarjimasi: "saqlangan holat ko'chirilmadi, chunki migrate funksiyasi berilmagan". Keyin saqlangan holatni tashlab yuboradi — mehmonning sozlamalari yo'qoladi. Shakl o'zgarsa — doim ikkalasi birga.
2.5 Boshqa tab — storage hodisasi
Mehmon saytni ikki tabda ochgan. Birinchisida «ixcham» ni yoqdi. Ikkinchisi buni bilmaydi: persist faqat store yaratilganda o'qiydi. localStorage darsidagi storage hodisasi — boshqa tab shu saytning localStorage ini o'zgartirganda keladi. Unga quloq solib, persist.rehydrate() — qayta o'qish — chaqiriladi. Kanondagi kod:
// Boshqa tabda sozlama o'zgardi — persist buni o'zi bilmaydi:
// storage hodisasi (faqat BOSHQA tablarda keladi) bilan qayta
// o'qiymiz
addEventListener("storage", (event) => {
if (event.key === SOZLAMALAR_KALITI) {
void useIlovaHolati.persist.rehydrate();
}
});Natija oynasi bitta tab, shuning uchun bu yerda jonli ko'rsatib bo'lmaydi. Kanonning brauzer tekshiruvi ikki tab bilan sinadi («Vazifalar qadami» bo'limida natijasi). persist ning boshqa yordamchilari: persist.hasHydrated() — o'qib bo'ldimi, persist.clearStorage() — yozuvni o'chirish, onRehydrateStorage — o'qish tugaganda chaqiriladigan funksiya.
Tekshirib ko'ring: Store:
{ lang: "uz", draft: "", setLang }.partializesizpersistulansa,localStorageda nima bo'ladi? Qaysi maydon saqlanmasligi kerak?
Javob
{"state":{"lang":"uz","draft":""},"version":0} — barcha ma'lumot maydonlari (funksiya setLang JSON'ga tushmaydi). draft — qoralama: uni saqlash ba'zan foydali (yozilgan matn yo'qolmasin), ba'zan zararli (birovning kompyuterida qolib ketadi). Qaror — sizniki, lekin uni partialize bilan ochiq qiling. Sukut bo'yicha "hammasi saqlanadi" — xavfli odat.
3. devtools va immer
3.1 devtools — har o'zgarish jurnalda
Redux DevTools — Chrome/Firefox kengaytmasi. U store'ning har o'zgarishini ro'yxat qilib ko'rsatadi: nomi, oldingi va keyingi holat, farqi. Hatto oldingi holatga "qaytib" ko'rish mumkin (time travel). Nomida Redux bo'lsa ham, Zustand unga devtools middleware orqali ulanadi:
import { create } from "zustand";
import { devtools } from "zustand/middleware";
const useCartStore = create(
devtools(
(set) => ({
items: [],
add: (name) =>
set(
(state) => ({ items: [...state.items, name] }),
undefined,
"savat/qoshish",
),
}),
{ name: "Bahor savati", enabled: import.meta.env.DEV },
),
);Uch yangi narsa:
setning uchinchi argumenti — amal nomi. Jurnalda "anonymous" o'rnigasavat/qoshishko'rinadi. Ikkinchi argument (undefined) — o'tgan darsdagi almashtirish bayrog'i.name— kengaytmada store nomi (bir nechta store bo'lsa, ajratish uchun).enabled: import.meta.env.DEV— faqat ishlab chiqishda.import.meta.env.DEV— Vite beradigan bayroq (Vite: muhit o'zgaruvchilari). Production'da kengaytmasi bor har kim store ichini ko'ra olmasin.
Kengaytma bo'lmasa, devtools jim ishlaydi — xato bermaydi. Saytdagi natija oynasida kengaytma yo'q, shuning uchun bu misol tugmasiz.
3.2 immer — "o'zgartirib" yozish
Reducer + Immer darsida Immer bilan tanishgan edik: qoralama (draft) ni bemalol o'zgartirasiz, Immer esa undan yangi o'zgarmas obyekt yasaydi. Zustand'da ham chuqur ichma-ich holatda spread piramidasi o'sadi:
set((state) => ({
branches: {
...state.branches,
chilonzor: {
...state.branches.chilonzor,
dishes: {
...state.branches.chilonzor.dishes,
osh: { ...state.branches.chilonzor.dishes.osh, price },
},
},
},
}));immer middleware bilan xuddi shu bitta qator:
import { create } from "zustand";
import { immer } from "zustand/middleware/immer";
const useMenuStore = create(
immer((set) => ({
branches: {
chilonzor: { dishes: { osh: { price: 35000, hot: true } } },
yunusobod: { dishes: { osh: { price: 35000, hot: true } } },
},
setPrice: (branch, dish, price) =>
set((state) => {
state.branches[branch].dishes[dish].price = price;
}),
})),
);
const before = useMenuStore.getState();
before.setPrice("chilonzor", "osh", 38000);
const after = useMenuStore.getState();
console.log(after.branches.chilonzor.dishes.osh.price);
console.log(before.branches.chilonzor.dishes.osh.price);
console.log(before.branches.yunusobod === after.branches.yunusobod);
console.log(Object.isFrozen(after.branches));zustand/middleware/immer alohida immer paketini talab qiladi (npm i immer), saytdagi natija oynasida u yo'q. Biz zustand 5.0.15 va immer 11.1.21 bilan Chrome 155 da ishga tushirdik:
38000
35000
true
trueQatorma-qator: yangi narx — 38 000. Eski holat (before) o'zgarmagan — 35 000. Tegilmagan filial (yunusobod) — o'sha havola: Immer faqat o'zgargan yo'lni nusxalaydi (strukturaviy bo'lishish), shuning uchun Yunusobod filialini ko'rsatadigan komponent chizilmaydi. Oxirgisi: natija muzlatilgan (frozen) — tasodifan to'g'ridan-to'g'ri o'zgartirib bo'lmaydi.
Updater ichida ikki yo'ldan bittasini tanlang: qoralamani o'zgartiring yoki yangi obyekt qaytaring. Ikkalasi birga (state.x = 1; return { y: 2 }) — Immer xato beradi.
3.3 Middleware tartibi
Bir nechta middleware — ichma-ich o'raladi. Zustand hujjati tavsiya qiladigan tartib: devtools eng tashqarida, immer eng ichkarida:
const useStore = create(
devtools(
persist(
immer((set) => ({ /* holat va amallar */ })),
{ name: "bahor" },
),
{ name: "Bahor" },
),
);Nega shunday? set chaqirilganda u tashqaridan ichkariga o'tadi. Rasmga qarang:
flowchart LR
A["amal: set(...)"] --> D["devtools: jurnalga yozadi"]
D --> P["persist: localStorage'ga saqlaydi"]
P --> I["immer: qoralamadan yangi obyekt"]
I --> S["store: yangi holat"]devtools tashqarida bo'lsa, boshqa middleware'larning ishini ham ko'radi (masalan, persist qayta o'qigan holatni). immer ichkarida bo'lsa, faqat sizning funksiyangizning set ini o'zgartiradi — boshqalar oddiy obyekt bilan ishlaydi. Tartib buzilsa ham ko'pincha ishlaydi, lekin TypeScript turlari va DevTools jurnali chalkashadi.
Tekshirib ko'ring:
immerbilan:set((s) => { s.items.push("Osh"); }). Shu kodimmersiz ishlaydimi?
Javob
Xatosiz bajariladi, lekin noto'g'ri ishlaydi. immer siz s — haqiqiy holat, push uni joyida o'zgartiradi va updater undefined qaytaradi. Zustand'ning set i "hech narsa o'zgarmadi" deb oladi, items havolasi o'sha — selektorlar o'zgarishni ko'rmaydi va ekran yangilanmaydi. Immer'siz — doim yangi massiv: set((s) => ({ items: [...s.items, "Osh"] })).
4. Slice naqshi va TypeScript
4.1 Store'ni bo'laklarga bo'lish
Slice (bo'lak) — store'ning bitta mavzuga tegishli qismi (holat va amallari) alohida funksiyada. Har bo'lak (set, get) oladi va o'z qismini qaytaradi. Store — bo'laklarning yig'indisi:
import { create } from "zustand";
const createCartSlice = (set, get) => ({
items: [],
add: (name) =>
set((state) => ({ items: [...state.items, name] })),
checkout: () => {
const { items, guest } = get();
console.log(`${guest}: ${items.join(", ")}`);
set({ items: [] });
},
});
const createGuestSlice = (set) => ({
guest: "Malika",
rename: (guest) => set({ guest }),
});
const useBahorStore = create((...a) => ({
...createCartSlice(...a),
...createGuestSlice(...a),
}));
export default function App() {
const count = useBahorStore((state) => state.items.length);
const add = useBahorStore((state) => state.add);
const checkout = useBahorStore((state) => state.checkout);
return (
<p>
<button onClick={() => add("Osh")}>Osh</button>{" "}
<button onClick={checkout}>Buyurtma</button> ({count})
</p>
);
}Konsolda:
Malika: OshEkranda:
Osh Buyurtma (0)(...a) — create bergan barcha argumentlar (set, get va store API) (rest va spread). Ular har bo'lakka aynan uzatiladi. Muhimi: bo'laklar bitta store'da. checkout savat bo'lagida yozilgan, lekin get() butun holatni beradi — mehmon ismini ham. Bo'lak — kodni fayllarga bo'lish usuli, ma'lumotni ajratish emas.
Nega bir nechta mustaqil store emas? Ikki store bo'lsa, checkout ikkinchisini import qilib o'qishi kerak, persist ikki marta sozlanadi, testda ikkalasini tozalash kerak. Zustand hujjati: "bitta store, kerak bo'lsa bo'laklarga bo'lingan". Butunlay bog'liq bo'lmagan narsalar uchun (masalan, alohida modulning ichki holati) alohida store ham normal.
4.2 TypeScript: create<T>()(...)
TypeScript bilan Zustand'ning odatiy yozuvi g'alati ko'rinadi — ikki qavs:
import { create } from "zustand";
type CartState = {
items: string[];
add: (name: string) => void;
};
const useCartStore = create<CartState>()((set) => ({
items: [],
add: (name) =>
set((state) => ({ items: [...state.items, name] })),
}));
useCartStore.getState().add(42);Xato:
TS2345: Argument of type 'number' is not assignable to parameter of type 'string'.Holat turini (CartState) siz berasiz. Middleware'lar esa store turini o'zgartiradi: persist hook'ga .persist xususiyatini qo'shadi, devtools — set ga uchinchi argumentni. Bu turlarni TypeScript o'zi chiqarishi kerak. Lekin TypeScript'da generik funksiyaning turlarini "qisman" berib bo'lmaydi: bittasini qo'lda bersangiz, qolganini ham berishingiz shart (Generiklar). create<CartState>() — birinchi chaqiruv faqat holat turini oladi. Ikkinchisi (...) — middleware'li funksiyani oladi va uning turlarini TypeScript o'zi chiqaradi. Ya'ni ikki qavs — tilning cheklovini aylanib o'tish (currying usuli bilan).
Bitta qavsli create<CartState>((set) => …) ham middleware'siz ishlaydi. Lekin persist qo'shilgan zahoti turlar buziladi. Shuning uchun hujjat va kanon doim ikki qavsni yozadi.
4.3 Slice'ning turi: StateCreator
Slice'larni TypeScript bilan yozganda har bo'lakka tur kerak. Zustand uni StateCreator beradi: StateCreator<ButunStore, Middlewarelar, [], ShuBo'lak>. Kanondagi ilova-holati.ts dan:
// Slice (19/#22): har qism — o'z funksiyasi, store — ularning
// yig'indisi. Tur parametrlari — persist middleware ichida
// ishlashi uchun
type Qism<T> = StateCreator<
IlovaHolati,
[["zustand/persist", unknown]],
[],
T
>;Birinchi parametr — butun store (get() butun holatni qaytarishi uchun). Ikkinchisi — bo'lak qaysi middleware'lar ichida ishlashi: bu yerda persist. Agar devtools ham bo'lsa — [["zustand/devtools", never], ["zustand/persist", unknown]]. Oxirgisi — shu bo'lak qaytaradigan qism. Bu turni bir marta yozib, keyin har bo'lak uchun Qism<ElonQismi>, Qism<SozlamaQismi> deysiz.
Tekshirib ko'ring: Slice misolida
createGuestSlicedagirenamesavat bo'lagidagiitemsni ko'ra oladimi? Qanday?
Javob
Ha. createGuestSlice ga ham get keladi ((...a) — hamma argumentlar har bo'lakka). (set, get) => ({ rename: (g) => { console.log(get().items); … } }) — butun store'ni o'qiydi. Bo'laklar orasida devor yo'q — bu qulay, lekin ehtiyot bo'ling: bo'laklar bir-birining ichiga ko'p qarasa, ajratishdan foyda qolmaydi.
5. SSR tuzoqlari
SSR (Server-Side Rendering) — sahifani avval serverda HTML qilib chizish, keyin brauzerda uni jonlantirish (hydration). Kursda u Next.js bilan 23-qismda keladi. vazifalar da SSR yo'q, lekin bugungi store kodi u yerga ko'chganda ikki joyda qoqiladi. Hozir bilib qo'yamiz.
5.1 Birinchi tuzoq: umumiy store
Brauzerda modul bir marta yuklanadi va bitta mehmonga xizmat qiladi. Serverda esa Node jarayoni hamma so'rovlarga xizmat qiladi va modul ham bir marta yuklanadi. Modul darajasidagi create(...) — butun server uchun bitta store.
Tasavvur qiling: Malika savatga osh qo'shdi. Server Malikaning sahifasini chizish uchun umumiy store'ga yozdi. Shu payt Bobur sahifani ochdi — server unga ham o'sha store'dan chizdi. Bobur Malikaning savatini ko'rdi. Bu nafaqat xato, balki ma'lumot sizib chiqishi (data leak).
Davosi — har so'rovga alohida store: createStore (o'tgan darsdagi vanilla store) + Context. Store'ni Provider yaratadi va useRef/useState da saqlaydi. Natija oynasida ikki "so'rov" ni yonma-yon ko'rsatamiz:
import { createContext, use, useState } from "react";
import { createStore, useStore } from "zustand";
const createCartStore = (guest) =>
createStore((set) => ({
guest,
items: [],
add: (name) =>
set((state) => ({ items: [...state.items, name] })),
}));
const CartContext = createContext(null);
function CartProvider({ guest, children }) {
// har Provider — o'z store'i (har so'rov, har mehmon)
const [store] = useState(() => createCartStore(guest));
return <CartContext value={store}>{children}</CartContext>;
}
function useCart(selector) {
const store = use(CartContext);
if (store === null) throw new Error("CartProvider yo'q");
return useStore(store, selector);
}
function Cart() {
const guest = useCart((state) => state.guest);
const items = useCart((state) => state.items);
const add = useCart((state) => state.add);
return (
<p>
<button onClick={() => add("Osh")}>{guest}: +Osh</button>{" "}
savat: {items.join(", ") || "bo'sh"}
</p>
);
}
export default function App() {
return (
<>
<CartProvider guest="Malika">
<Cart />
</CartProvider>
<CartProvider guest="Bobur">
<Cart />
</CartProvider>
</>
);
}Ekranda:
Malika: +Osh savat: Osh
Bobur: +Osh savat: bo'shMalikaning bosishi Boburning savatiga tegmadi. useState(() => …) — dangasa boshlash: store faqat birinchi chizishda bir marta yaratiladi. use(CartContext) — use() darsidagi Context o'qish. Selektorlar va useShallow — o'sha-o'sha, faqat useStore(store, selektor) orqali.
Zustand'ning Next.js qo'llanmasi aynan shu naqshni beradi. Context bu yerda qiymatni emas, store havolasini tarqatadi. Havola o'zgarmaydi — Context sabab bo'lgan qayta chizish yo'q. Qayta chizishni yana selektor boshqaradi. Context va Zustand bir-birini to'ldirdi.
5.2 Ikkinchi tuzoq: hydration paytidagi birinchi kadr
Server mehmonning localStorage ini bilmaydi — u faqat brauzerda. Server sahifani standart sozlama bilan chizadi: "oddiy". Brauzerda esa persist "ixcham" ni o'qiydi. Jonlantirishda React serverdagi HTML'ni brauzerdagi birinchi chizish bilan solishtiradi. Ular farq qilsa — hydration xatosi.
Zustand 5 buni hisobga olgan. Natija oynasida serverdan kelgan HTML'ni qo'lda yozib, ustidan hydrateRoot qilamiz:
// mehmon kecha «ixcham» ni tanlagan
localStorage.setItem(
"bahor:sozlamalar",
JSON.stringify({ state: { compact: true }, version: 0 }),
);
import { hydrateRoot } from "react-dom/client";
import { create } from "zustand";
import { persist } from "zustand/middleware";
const useSettingsStore = create(
persist(() => ({ compact: false }), { name: "bahor:sozlamalar" }),
);
function App() {
const compact = useSettingsStore((state) => state.compact);
console.log("chizildi:", compact ? "ixcham" : "oddiy");
return <p>{`Menyu: ${compact ? "ixcham" : "oddiy"}`}</p>;
}
const s = useSettingsStore;
console.log("store'da:", s.getState().compact);
console.log("boshlang'ich:", s.getInitialState().compact);
// server yuborgan HTML (u localStorage'ni bilmaydi)
const root = document.getElementById("root");
root.innerHTML = "<p>Menyu: oddiy</p>";
hydrateRoot(root, <App />);Konsolda:
store'da: true
boshlang'ich: false
chizildi: oddiy
chizildi: ixchamEkranda:
Menyu: ixchamXato yo'q, lekin ikki chizish bor. Sababi — useSyncExternalStore darsidagi uchinchi argument, getServerSnapshot. Zustand unga getInitialState() ni beradi — persist o'qishidan oldingi holat. Jonlantirish paytida React aynan shu bilan chizadi: "oddiy" — server HTML'iga mos. Darhol keyin haqiqiy holat bilan qayta chizadi: "ixcham".
Demak tuzoq — xato emas, miltillash: mehmon bir lahza "oddiy" menyuni ko'radi, keyin u "ixcham" ga sakraydi. Shunday ma'lumotni (tema, ixcham rejim) Next.js'da odatda serverga ham ma'lum qilishadi (cookie orqali), yoki birinchi kadrni maxsus yashirishadi. Bu 23-qism mavzusi.
Yana bir sozlama: persist(…, { skipHydration: true }) — persist o'zi o'qimaydi, siz useEffect ichida useSettingsStore.persist.rehydrate() ni chaqirasiz. Bu o'qish vaqtini to'liq o'zingiz boshqarishingiz kerak bo'lganda kerak.
vazifalar (Vite, SSR yo'q) |
Next.js (SSR) | |
|---|---|---|
| store soni | bitta, modul darajasida | har so'rovga — Provider ichida |
persist o'qishi |
store yaratilganda, sinxron | brauzerda, jonlantirishdan keyin |
| birinchi kadr | to'g'ri sozlama | standart qiymat → miltillash |
Tekshirib ko'ring: Nega
vazifalardaHimoyalangansahifadagi foydalanuvchi ma'lumoti bilan "boshqa mehmonning savati" muammosi chiqmaydi, garchi store modul darajasida bo'lsa ham?
Javob
vazifalar — faqat brauzerda ishlaydigan SPA. Har mehmonning brauzeri modulni o'zi yuklaydi va o'z store'iga ega. Umumiy store muammosi faqat bitta jarayon ko'p foydalanuvchiga sahifa chizganda chiqadi — serverda. Shuning uchun kanon modul darajasidagi create ni ishlatadi va bu to'g'ri.
6. Vazifalar qadami: sozlamalar — store'da, commit (holat 22)
6.1 Nima quramiz
O'tgan darsda e'lonni store'ga ko'chirdik. Bugun ikki foydalanuvchi sozlamasi:
- «O'chirishdan oldin so'rash» — o'chirish dialogi (dialog). Ko'p vazifani tez tozalaydigan foydalanuvchi uni o'chirib qo'yishi mumkin.
- «Ixcham ro'yxat» — qatorlar orasidagi bo'shliq kichikroq.
Ikkalasi persist bilan saqlanadi, e'lon esa yo'q. Sozlamalar sahifasi (React Router I da yaratilgan) ularni o'zgartiradi, ro'yxat sahifasi o'qiydi.
6.2 To'liq store: ilova-holati.ts
O'tgan darsdagi e'lon qismiga sozlama qismi va persist qo'shiladi. Kanondan (o'tgan darsda ko'rilgan bosh izoh va e'lon qismi o'rniga …):
import { create } from "zustand";
import type { StateCreator } from "zustand";
import { createJSONStorage, persist } from "zustand/middleware";
type Zichlik = "oddiy" | "ixcham";
type SozlamaQismi = {
/** o'chirishdan oldin dialog (13/#09) — o'chirib qo'ysa
* bo'ladi */
readonly ochirishdaSora: boolean;
readonly zichlik: Zichlik;
setOchirishdaSora: (qiymat: boolean) => void;
setZichlik: (qiymat: Zichlik) => void;
};
export type IlovaHolati = ElonQismi & SozlamaQismi;
// … Qism<T> turi (yuqorida), ELON_MUDDATI, elonQismi …
const sozlamaQismi: Qism<SozlamaQismi> = (set) => ({
ochirishdaSora: true,
zichlik: "oddiy",
setOchirishdaSora: (qiymat) => set({ ochirishdaSora: qiymat }),
setZichlik: (qiymat) => set({ zichlik: qiymat }),
});
export const SOZLAMALAR_KALITI = "vazifalar:sozlamalar";
/**
* persist (19/#22): sozlamalar localStorage'da — faqat ular
* (partialize), e'lon emas. version — shakl o'zgarsa, eski yozuvni
* ko'chirish (migrate) uchun. Saqlangan qiymat store yaratilganda
* SINXRON o'qiladi — birinchi kadrdayoq to'g'ri sozlama (SSR'da
* bunday emas — #22 tuzog'i)
*/
export const useIlovaHolati = create<IlovaHolati>()(
persist((...a) => ({ ...elonQismi(...a), ...sozlamaQismi(...a) }), {
name: SOZLAMALAR_KALITI,
version: 1,
storage: createJSONStorage(() => localStorage),
partialize: (h) => ({
ochirishdaSora: h.ochirishdaSora,
zichlik: h.zichlik,
}),
}),
);
// … storage hodisasi → rehydrate() («Boshqa tab» bo'limida) …Bugungi darsning hamma qismi bitta faylda: ikki slice (elonQismi, sozlamaQismi) — Qism<T> turi bilan; create<IlovaHolati>() — ikki qavs; persist — name, version, storage, partialize; oxirida boshqa tab uchun storage hodisasi.
Kalit nomi vazifalar:sozlamalar — 13-qismdan beri ishlatilayotgan vazifalar: prefiksi bilan (localStorage darsidagi kelishuv). version: 1 — migrate hali yo'q, chunki eski yozuv yo'q. Kelajakda shakl o'zgarsa, version: 2 va migrate qo'shiladi. devtools va immer kanonda yo'q: store kichik va tekis — immer kerak emas, devtools o'rniga React DevTools yetadi (u hook qiymatini ko'rsatadi).
6.3 Sozlamalar sahifasi
SozlamalarSahifasi.tsx ga ikki katakcha (kanon):
const ochirishdaSora = useIlovaHolati((h) => h.ochirishdaSora);
const zichlik = useIlovaHolati((h) => h.zichlik);
const setOchirishdaSora = useIlovaHolati((h) => h.setOchirishdaSora);
const setZichlik = useIlovaHolati((h) => h.setZichlik);
return (
<>
<title>Sozlamalar — Vazifalar</title>
<h2 ref={sarlavhaRef} tabIndex={-1}>
Sozlamalar
</h2>
<section id="sozlamalar" aria-labelledby={sarlavhaId}>
<h3 id={sarlavhaId}>Ro'yxat</h3>
<p>
<input
type="checkbox"
id="ochirishda-sora"
checked={ochirishdaSora}
onChange={(event) =>
setOchirishdaSora(event.target.checked)
}
/>{" "}
<label htmlFor="ochirishda-sora">
O'chirishdan oldin so'rash
</label>
</p>
{/* «Ixcham ro'yxat» — xuddi shunday, setZichlik bilan */}
</section>To'rt selektor — har biri bitta qiymat yoki funksiya (o'tgan darsdagi qoida). <section aria-labelledby> — Malika (ekran o'quvchi) bo'limni sarlavhasi bilan eshitadi (useId bilan bog'langan).
6.4 Ro'yxat sozlamani o'qiydi
App.tsx da ikki selektor va ikki o'zgarish (kanon):
+ // 19/#22: foydalanuvchi sozlamalari (Sozlamalar sahifasi)
+ const ochirishdaSora = useIlovaHolati((h) => h.ochirishdaSora);
+ const zichlik = useIlovaHolati((h) => h.zichlik);
...
const zaxira = qoshniId(korinadigan, id);
+ // 19/#22: sozlamada "so'ramasin" tanlangan — darhol
+ if (!ochirishdaSora) {
+ ochir({ id, matn: vazifa.matn, zaxira });
+ return;
+ }
...
- className={eskirgan ? "eskirgan" : undefined}
+ className={
+ [eskirgan && "eskirgan", zichlik === "ixcham" && "ixcham"]
+ .filter(Boolean)
+ .join(" ") || undefined
+ }Dialog tasdiqlagan o'chirish va "so'ramasdan" o'chirish endi bitta ochir() funksiyasidan o'tadi — kod takrorlanmasin. CSS'ga 4 qator: #royxat.ixcham li ning ichki bo'shlig'i kichikroq.
6.5 Testlar va tekshiruvlar
Yangi IlovaHolati.test.tsx — store'ning o'zi, React'siz (kanon, bitta test):
test("persist: sozlamalar localStorage'da, e'lon — yo'q (partialize)", () => {
useIlovaHolati.getState().setOchirishdaSora(false);
useIlovaHolati.getState().elonQil("Saqlanmasin");
const saqlangan = JSON.parse(
localStorage.getItem(SOZLAMALAR_KALITI) ?? "null",
) as unknown;
expect(saqlangan).toEqual({
state: { ochirishdaSora: false, zichlik: "oddiy" },
version: 1,
});
});Qolgan ikki test: e'lon 3 soniyada o'chishi (soxta taymer bilan) va boshqa tab (StorageEvent yuboriladi, zichlik "ixcham" bo'lishi kutiladi). Har testdan keyin afterEach sozlamalarni boshlang'ich qiymatga qaytaradi — o'tgan darsdagi "testlar bir-biriga ta'sir qilmasin" qoidasi.
git add -A
git commitCommit (kanon): feat: e'lon va sozlamalar — Zustand store (slice, persist), sahifalararo e'lon.
Tekshiruvlar (haqiqiy, holat 22):
npm test— node:testℹ tests 177 · ℹ pass 177, VitestTests 59 passed (59)(+3 store testi). «Orqaga» tuzatishidan (React Router III) keyin kanonda — node:test 185.lint,format:check,tip,knip—exit 0.- Build:
index-….js 421.00 kB │ gzip: 132.77 kB(holat 18 da 131.31). Zustand va yangi kod — taxminan +1.4 KB gzip; kutubxonaning o'zi — +1.3 KB. - Brauzer: 13–17-qism tekshiruvlari 159/159, 18-qismniki 72/72, 19-qismniki 72/72 («Orqaga» va
/kirishtuzatishlaridan keyin 75). Yangi yettitasi:
✅ action'dan e'lon (getState) — ro'yxat sahifasida ko'rindi: "«Kitob o'qish» o'chirildi."
✅ sozlamalar: localStorage'da faqat ikki sozlama (persist, partialize): {"state":{"ochirishdaSora":false,"zichlik":"ixcham"},"version":1}
✅ boshqa tab: sozlama o'zgarishi yetib keldi (storage → rehydrate): false
✅ ixcham ro'yxat — class: "ixcham"
✅ so'ramasdan o'chirildi: dialog ochilmadi, e'lon, serverda yo'q: [false,"«Non olish #bozor» o'chirildi.",[2]]
✅ qayta ochilganda sozlama saqlangan (birinchi kadrdayoq): "ixcham"
✅ Zustand: CSP buzilishi yo'q: []Uchinchi qator — ikki tab: birinchisida «O'chirishdan oldin so'rash» o'chirildi, ikkinchisi storage hodisasi orqali false ni oldi. Oltinchi qator — sahifa qayta ochilganda birinchi kadrdayoq "ixcham" (SSR yo'q — persist sinxron). Oxirgisi — CSP ostida Zustand hech qanday taqiqlangan ish qilmadi (eval yo'q). Diff (holat 18 dan): 11 fayl, +296 −46.
7. Ko'p uchraydigan xatolar
| Belgi | Sabab | Davosi |
|---|---|---|
| sozlama yangilanishdan keyin yo'qoladi | persist ulanmagan yoki name har safar boshqa |
bitta doimiy name |
localStorage da keraksiz (e'lon, qoralama) yozuv |
partialize yo'q — hammasi saqlanadi |
partialize: (s) => ({ … }) |
| shakl o'zgargach eski mehmonlarda ilova yiqiladi | eski yozuv yangi kod bilan o'qildi | version ni oshiring + migrate |
State loaded from storage couldn't be migrated since no migrate function was provided |
version oshdi, migrate yo'q — sozlamalar tashlandi |
har versiya o'zgarishiga migrate |
| boshqa tabdagi o'zgarish ko'rinmaydi | persist faqat yaratilganda o'qiydi |
storage hodisasi → persist.rehydrate() |
immer bilan push qilindi, lekin ekran eski |
immer middleware ulanmagan |
immer(...) bilan o'rang yoki yangi massiv |
TypeScript'da persist qo'shilgach tur xatolari |
create<T>(…) — bitta qavs |
create<T>()(…) |
| Next.js'da bir mehmon boshqasining ma'lumotini ko'radi | modul darajasidagi store serverda umumiy | har so'rovga createStore + Context |
8. Mashqlar
Mashqlar kurs/mashqlar/19/22-zustand-middleware/ da. Har yechimni «Ishga tushir» bilan sinang.
1-mashq (oson): faqat tema saqlansin
Store'da theme va search bor. Qidiruv matni saqlanmasligi kerak. Bo'sh joyni to'ldiring:
persist(yaratuvchi, {
name: "bahor:menyu",
[___:partialize]: (state) => ({ theme: state.theme }),
});Yechim
import { create } from "zustand";
import { persist } from "zustand/middleware";
const useMenuStore = create(
persist(
(set) => ({
theme: "oq",
search: "",
setTheme: (theme) => set({ theme }),
setSearch: (search) => set({ search }),
}),
{
name: "bahor:menyu",
partialize: (state) => ({ theme: state.theme }),
},
),
);
export default function App() {
const setTheme = useMenuStore((state) => state.setTheme);
const setSearch = useMenuStore((state) => state.setSearch);
return (
<>
<input
aria-label="Qidiruv"
onChange={(event) => setSearch(event.target.value)}
/>
<button onClick={() => setTheme("qora")}>Qora tema</button>{" "}
<button onClick={showStorage}>Xotira</button>
</>
);
}
function showStorage() {
const { search } = useMenuStore.getState();
console.log("store'da:", search);
console.log(localStorage.getItem("bahor:menyu"));
}Konsolda:
store'da: osh
{"state":{"theme":"qora"},"version":0}Qidiruv matni store'da bor, xotirada — yo'q: persist har set dan keyin yozdi, lekin faqat partialize qaytargan theme ni. showStorage — oddiy funksiya, shuning uchun getState() bilan o'qiydi (o'tgan darsdagi qoida).
2-mashq (o'rta): lang → language
«Bahor» saytining eski versiyasi tilni { lang: "uz" } deb saqlagan (versiya 0). Yangi kodda maydon nomi language. Boshlang'ich kodda eski yozuv tayyor. version va migrate qo'shing, shunda ekranda Til: uz chiqsin (hozir Til: en — standart qiymat, mehmonning tanlovi yo'qoldi).
localStorage.setItem(
"bahor:til",
JSON.stringify({ state: { lang: "uz" }, version: 0 }),
);
import { create } from "zustand";
import { persist } from "zustand/middleware";
const useLangStore = create(
persist(() => ({ language: "en" }), { name: "bahor:til" }),
);
export default function App() {
const language = useLangStore((state) => state.language);
return <p>Til: {language}</p>;
}Ekranda:
Til: enYechim
localStorage.setItem(
"bahor:til",
JSON.stringify({ state: { lang: "uz" }, version: 0 }),
);
import { create } from "zustand";
import { persist } from "zustand/middleware";
const useLangStore = create(
persist(() => ({ language: "en" }), {
name: "bahor:til",
version: 1,
migrate: (old, version) => {
if (version < 1) {
return { language: old.lang };
}
return old;
},
}),
);
export default function App() {
const language = useLangStore((state) => state.language);
return <p>Til: {language}</p>;
}
console.log(localStorage.getItem("bahor:til"));Konsolda:
{"state":{"language":"uz"},"version":1}Ekranda:
Til: uzBoshlang'ich kodda nega en edi? Saqlangan { lang: "uz" } boshlang'ich { language: "en" } bilan birlashtirildi. Natijada ikkala maydon ham bor edi, lekin komponent language ni o'qidi. migrate eski maydonni yangi nomga ko'chiradi va yozuvni darhol 1-versiya bilan qayta saqlaydi.
3-mashq (qiyin): ikki slice va TypeScript'siz tur
Store'ni ikki bo'lakdan yig'ing:
createOrderSlice—items(taom nomlari),add(name),submit().submitsavat bo'sh bo'lmasa konsolga<stol>-stol: <taomlar>yozadi va savatni tozalaydi.createTableSlice—table(boshida 4),nextTable()— stol raqami +1.submitstol raqamini boshqa bo'lakdanget()bilan oladi.
Tugmalar: «Osh», «Keyingi stol», «Yuborish». Ishora: «Store'ni bo'laklarga bo'lish» bo'limidagi misol.
Yechim
import { create } from "zustand";
const createOrderSlice = (set, get) => ({
items: [],
add: (name) =>
set((state) => ({ items: [...state.items, name] })),
submit: () => {
const { items, table } = get();
if (items.length === 0) return;
console.log(`${table}-stol: ${items.join(", ")}`);
set({ items: [] });
},
});
const createTableSlice = (set) => ({
table: 4,
nextTable: () => set((state) => ({ table: state.table + 1 })),
});
const useOrderStore = create((...a) => ({
...createOrderSlice(...a),
...createTableSlice(...a),
}));
export default function App() {
const table = useOrderStore((state) => state.table);
const add = useOrderStore((state) => state.add);
const next = useOrderStore((state) => state.nextTable);
const submit = useOrderStore((state) => state.submit);
return (
<p>
{table}-stol:{" "}
<button onClick={() => add("Osh")}>Osh</button>{" "}
<button onClick={next}>Keyingi stol</button>{" "}
<button onClick={submit}>Yuborish</button>
</p>
);
}Konsolda:
4-stol: Osh
5-stol: Osh, OshEkranda:
5-stol: Osh Keyingi stol Yuborishsubmit buyurtma bo'lagida, table — stol bo'lagida, lekin get() ikkalasini birga beradi: store bitta.
4-mashq: Vazifalar qadami
feat/zustand branch'ida «Vazifalar qadami» bo'limidagi o'zgarishlarni yakunlang: ilova-holati.ts (slice'lar, persist, storage hodisasi), Sozlamalar sahifasidagi ikki katakcha, App.tsx dagi ochirishdaSora va zichlik, CSS, IlovaHolati.test.tsx. Keyin commit.
Yechim
Tekshiruv ro'yxati:
npm test— node:test 185 («Orqaga» tuzatishigacha — 177), Vitest 59 (hammasipass).npm run dev— Sozlamalarda «Ixcham ro'yxat» ni yoqing, ro'yxatga qayting: qatorlar zichroq. Sahifani yangilang — sozlama saqlangan, miltillash yo'q.- DevTools → Application → Local Storage:
vazifalar:sozlamalar—{"state":{…},"version":1}, e'lon yo'q. - Saytni ikkinchi tabda oching, birinchisida sozlamani o'zgartiring — ikkinchi tabga o'tsangiz, Sozlamalar sahifasida yangi qiymat.
- «O'chirishdan oldin so'rash» o'chiq bo'lsa — o'chirish dialogsiz, e'lon bilan.
git log --oneline -1—feat: e'lon va sozlamalar — Zustand store (slice, persist), sahifalararo e'lon.
9. Real ishda
persist— savat, tema, til, "yangiliklarni ko'rdim" belgilari, qoralama formalar. Katta ma'lumot (minglab yozuv, rasm) uchun — IndexedDB (keyingi darslardan birida).devtools— jamoaviy loyihalarda deyarli majburiy: "bu qiymat qayerdan keldi?" savoliga jurnal javob beradi. Amal nomlari (savat/qoshish) ni izchil yozish — kod ko'rigida so'raladi.- Next.js — Zustand'ning rasmiy qo'llanmasi "har so'rovga store, Context orqali" naqshini beradi. Server komponentlarida (Server Components) store umuman ishlatilmaydi — u faqat klient komponentlar uchun.
- Intervyu savollari: "persist'da eski ma'lumotni qanday ko'chirasiz?", "nega
create<T>()()?", "Next.js'da Zustand store'ni qanday yaratasiz va nega global emas?", "middleware tartibi nimaga ta'sir qiladi?".
Xulosa
- Middleware — store yaratuvchini o'rab, imkoniyat qo'shadi:
persist,devtools,immer; tartib —devtoolstashqarida,immerichkarida. persist:name— kalit,partialize— nimani saqlash,version+migrate— eski yozuvni ko'chirish, boshqa tab —storagehodisasi varehydrate().immer— chuqur holatni "o'zgartirib" yozish; natija baribir yangi va muzlatilgan obyekt.- Slice — bitta store'ni fayllarga bo'lish;
get()butun holatni beradi. TypeScript'da —create<T>()(…)vaStateCreator. - SSR: serverda modul darajasidagi store umumiy → har so'rovga
createStore+ Context; jonlantirishda birinchi kadr — standart qiymat (miltillash). vazifalar: e'lon va ikki sozlama — bitta store,persistfaqat sozlamalarni saqlaydi; commit, holat 22.
Keyingi dars: Redux Toolkit I: store, slice va hooklar — Flux oqimi, configureStore, createSlice, useSelector/useDispatch; Redux qaysi loyihada to'g'ri tanlov va biz uni nega kanonga olmadik.
Manbalar
- Zustand: Persisting store data (
partialize,version,migrate,skipHydration) — zustand.docs.pmnd.rs/integrations/persisting-store-data (v5) - Zustand: Middlewares —
devtools,immer— zustand.docs.pmnd.rs/middlewares - Zustand: Slices pattern; TypeScript guide (
create<T>()) — zustand.docs.pmnd.rs/guides/slices-pattern, /guides/typescript - Zustand: Setup with Next.js — zustand.docs.pmnd.rs/guides/nextjs
- React:
hydrateRoot,useSyncExternalStore(getServerSnapshot) — react.dev/reference
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!