Mundarija (26)
- Bu darsda
- 1. Nega bu kerak?
- 2. Yetti joy
- 2.1 Local state — bitta komponentniki
- 2.2 Ko'tarilgan state — bir nechta yaqin komponent
- 2.3 Context — chuqur daraxt bo'ylab
- 2.4 URL — ulashsa bo'ladigan holat
- 2.5 Server keshi — serverdan kelgan ma'lumot
- 2.6 Forma state — yozilayotgan narsa
- 2.7 Global store — uzoq komponentlar va React'dan tashqarisi
- 3. Hisoblanadigan qiymat — state emas
- 4. Qaror daraxti
- 5. Vazifalar qadami: vazifalar holatining xaritasi (holat 22 boshlanishi)
- 5.1 Hozirgi holat (holat 18)
- 5.2 Joyi noto'g'ri bo'lgan ikkitasi
- 5.3 Qaror (kanon)
- 5.4 Narxi
- 6. Ko'p uchraydigan xatolar
- 7. Mashqlar
- 1-mashq (oson): joy tanlang
- 2-mashq (o'rta): nusxani olib tashlang
- 3-mashq (qiyin): kichik store
- 4-mashq: Vazifalar qadami
- 8. Real ishda
- Xulosa
- Manbalar
React state turlari: local, URL, server keshi, forma va global store — qaysi biri qayerda
Qisqacha: Har state'ning o'z joyi bor. Bitta komponentniki —
useState. Bir nechta yaqin komponentniki — umumiy otaga ko'tariladi. Ulashsa yoki «Orqaga» bilan qaytsa bo'ladigan narsa (filtr, sahifa) — URL'da. Serverdan kelgani — TanStack Query keshida. Yozilayotgan forma — forma kutubxonasida. Uzoq komponentlar va React'dan tashqaridagi kod ham ko'radigan client state (mijoz holati) — global store'da. Hisoblanadigan qiymat esa umuman state emas.
Bu darsda
- Yetti xil state'ni (local, ko'tarilgan, Context, URL, server keshi, forma, global store) misol bilan ajratasiz.
- Hisoblanadigan qiymatni state'ga yozish va server ma'lumotini
useStatega nusxalash nega xato ekanini jonli misolda ko'rasiz. - Qaror daraxti bilan har yangi state uchun joy tanlaysiz.
- React'dan tashqaridagi store g'oyasini
useSyncExternalStorebilan ko'rasiz — keyingi darsdagi Zustand'ning yuragi. vazifalardagi hamma state'ning xaritasini tuzasiz va 19-qism oxirigacha nima qayerga ko'chishini bilasiz.
Oldin bilishingiz kerak: Server state vs client state, Context II: reducer bilan birga, performance va chegaralar, URL — holat saqlash joyi, TanStack Query I, useSyncExternalStore.
1. Nega bu kerak?
17-qismdan beri vazifalar da state ko'paydi: ro'yxat, filtr, saralash, qidiruv matni, tahrirdagi vazifa, e'lon, tema, kirgan foydalanuvchi, internet holati, forma qoralamasi. Har biri bir joyda yashaydi. Ba'zisi to'g'ri joyda, ba'zisi — tarixiy sabab bilan.
Noto'g'ri joy ikki xil xato beradi. Birinchisi — eskirish: bir ma'lumotning ikki nusxasi, ulardan biri yangilanmaydi. Server state vs client state dagi "serverda 4, statistikada 3" shunday edi. Ikkinchisi — ortiqcha murakkablik: hamma narsa bitta global store'da, har kichik o'zgarishda yarim ilova qayta chiziladi va kim nimani o'zgartirgani noma'lum.
Bugungi dars — yangi kutubxona emas, xarita. Keyingi darslarda Zustand, Redux, Jotai, React Hook Form keladi. Ularning har biri bitta state turi uchun. Qaysi biri qachon kerakligini bilmasangiz, kutubxona yordam emas, yuk bo'ladi.
O'xshatish: uydagi narsalar. Tish cho'tkasi — vannada (faqat sizniki, bir joyda ishlatiladi). Kalit — eshik oldidagi ilgakda (hamma oila a'zosi oladi). Hujjatlar — seyfda (muhim, nusxasi bankda ham bor). Hammasini bitta katta qutiga solish mumkin. Lekin ertalab tish cho'tkasini qutidan izlash — vaqt.
2. Yetti joy
2.1 Local state — bitta komponentniki
Eng oddiy va eng ko'p uchraydigan. Taom kartochkasidagi porsiya soni faqat shu kartochkaga tegishli:
import { useState } from "react";
function DishCard({ name }) {
const [portions, setPortions] = useState(1);
return (
<p>
{name}: {portions}{" "}
<button onClick={() => setPortions(portions + 1)}>+</button>
</p>
);
}
export default function App() {
return <DishCard name="Osh" />;
}Ekranda:
Osh: 3 +Qoida: state'ni ishlatadigan joyga iloji boricha yaqin saqlang (colocation). Porsiyani App ga yoki global store'ga chiqarish — hech kim so'ramagan murakkablik. Ochiq/yopiq menyu, input matni, hover holati, animatsiya bosqichi — local state.
Yana bir "local" vosita — useRef (useRef I). U ekranga ta'sir qilmaydigan xotira: taymer id'si, oldingi qiymat, DOM element. O'zgarganda qayta chizish kerak bo'lmasa — state emas, ref.
2.2 Ko'tarilgan state — bir nechta yaqin komponent
Endi "Savatda: N ta" yozuvi menyudan boshqa komponentda. Ikkalasi bitta sonni ko'rishi kerak. State'ni ko'tarish darsidagi yechim — eng yaqin umumiy otaga:
import { useState } from "react";
function Menu({ onAdd }) {
return (
<p>
<button onClick={() => onAdd("Osh")}>Osh qo'shish</button>{" "}
<button onClick={() => onAdd("Manti")}>Manti qo'shish</button>
</p>
);
}
function CartBadge({ items }) {
return <p>Savatda: {items.length} ta</p>;
}
export default function App() {
const [items, setItems] = useState([]);
return (
<>
<CartBadge items={items} />
<Menu onAdd={(name) => setItems([...items, name])} />
</>
);
}Ekranda:
Savatda: 2 ta
Osh qo'shish Manti qo'shishKo'tarish oddiy va ochiq: kim nimani o'zgartirishi props'da ko'rinib turadi. Muammo — chuqurlik. Komponentlar orasida 5 qavat bo'lsa, items ni har qavatdan o'tkazish kerak — props drilling.
2.3 Context — chuqur daraxt bo'ylab
Props drilling'ning React'dagi javobi — Context. Lekin Context II darsidagi chegarani eslang: Context qiymati o'zgarsa, uni o'qiydigan hamma komponent qayta chiziladi. Shuning uchun Context kam o'zgaradigan narsa uchun yaxshi: tema, til, kirgan foydalanuvchi, "qaysi ilova" sozlamalari.
import { createContext, useContext, useState } from "react";
const ThemeContext = createContext("kunduzgi");
function DeepButton() {
const theme = useContext(ThemeContext);
return <p>Tugma rejimi: {theme}</p>;
}
function Layout() {
return (
<section>
<DeepButton />
</section>
);
}
export default function App() {
const [theme, setTheme] = useState("kunduzgi");
return (
<ThemeContext value={theme}>
<button onClick={() => setTheme("tungi")}>Tungi rejim</button>
<Layout />
</ThemeContext>
);
}Ekranda:
Tungi rejim
Tugma rejimi: tungiLayout theme ni bilmaydi va uzatmaydi — DeepButton uni to'g'ridan oladi. Context — uzatish vositasi, state boshqarish vositasi emas: state baribir useState da, Context faqat uni pastga yetkazadi.
Context'ning ikkinchi cheklovi: unga faqat komponent ichidan, hook bilan yetish mumkin. Marshrut action'i, oddiy funksiya, taymer — Context'ni ko'rmaydi. Bu «Global store» bo'limida muhim bo'ladi.
2.4 URL — ulashsa bo'ladigan holat
Filtr, saralash, sahifa raqami, tanlangan tab, qidiruv so'zi. Agar foydalanuvchi bu holatni havola bilan yuborishi yoki «Orqaga» bilan qaytishi mumkin bo'lsa — joyi URL. URL — holat saqlash joyi darsida vazifalar filtrini shunday ko'chirgan edik:
import {
createMemoryRouter,
RouterProvider,
useLocation,
useSearchParams,
} from "react-router";
const MENU = {
taomlar: "Osh, Manti",
ichimliklar: "Ko'k choy, Kompot",
};
function Menu() {
const [params, setParams] = useSearchParams();
const section = params.get("bolim") ?? "taomlar";
const { pathname, search } = useLocation();
return (
<>
<p>Manzil: {pathname + search}</p>
<button onClick={() => setParams({ bolim: "ichimliklar" })}>
Ichimliklar
</button>
<p>{MENU[section] ?? MENU.taomlar}</p>
</>
);
}
const router = createMemoryRouter([{ path: "/", element: <Menu /> }]);
export default function App() {
return <RouterProvider router={router} />;
}Ekranda:
Manzil: /?bolim=ichimliklar
Ichimliklar
Ko'k choy, KompotuseState yo'q — holat manzilning o'zida. /?bolim=ichimliklar ni do'stingizga yuborsangiz, u ham ichimliklarni ko'radi. Natija oynasida manzil satri yo'q, shuning uchun xotiradagi router va ekranda "Manzil:" qatori.
URL'ga nima tushmaydi: maxfiy narsa (token, parol — havola loglarda qoladi), katta ma'lumot (manzil ~2000 belgidan oshmasin), har harf bosilishida o'zgaradigan narsa (har biri tarixga yozuv).
2.5 Server keshi — serverdan kelgan ma'lumot
Ro'yxat, taom, foydalanuvchi profili, buyurtmalar. Bu ma'lumotning egasi — server. Brauzerdagi narsa — faqat nusxa, u eskiradi. Uchta darsda o'rgandik: joyi — TanStack Query keshi (TanStack Query I).
Bu yerda eng ko'p uchraydigan xato — server ma'lumotini useState ga nusxalash: "o'zgartirish uchun o'zimda bo'lsin". Ko'ramiz. «Yangilash» serverdan yangi narxni oladi:
import { useState } from "react";
import {
QueryClient,
QueryClientProvider,
useQuery,
} from "@tanstack/react-query";
const queryClient = new QueryClient();
let price = 35000;
function fetchPrice() {
const value = price;
price += 2000; // keyingi safar server boshqa narx beradi
return Promise.resolve(value);
}
// ❌ nusxa: birinchi qiymat bilan qotib qoladi
function CopiedPrice({ serverPrice }) {
const [shown] = useState(serverPrice);
return <p>Nusxa: {shown} so'm</p>;
}
function Prices() {
const { data, refetch } = useQuery({
queryKey: ["price"],
queryFn: fetchPrice,
});
if (data === undefined) return <p>Yuklanmoqda…</p>;
return (
<>
<p>Keshdan: {data} so'm</p>
<CopiedPrice serverPrice={data} />
<button onClick={() => refetch()}>Yangilash</button>
</>
);
}
export default function App() {
return (
<QueryClientProvider client={queryClient}>
<Prices />
</QueryClientProvider>
);
}Ekranda:
Keshdan: 37000 so'm
Nusxa: 35000 so'm
YangilashServer yangi narx berdi, kesh yangilandi — "Nusxa" esa eski. useState(serverPrice) dagi qiymat faqat birinchi chizishda o'qiladi. Keyin prop o'zgarsa ham state o'zgarmaydi (State I). Ekranda bir narsaning ikki xil narxi.
Qoida: server ma'lumotini nusxalamang — to'g'ridan keshdan o'qing. Uni tahrirlash kerak bo'lsa (forma), nusxa — forma state'i va u ataylab vaqtinchalik: saqlangach yoki bekor qilingach tashlanadi. Bu keyingi tur.
2.6 Forma state — yozilayotgan narsa
Foydalanuvchi forma to'ldiryapti: maydonlar qiymati, qaysi biriga tegilgan, qaysi birida xato, yuborilyaptimi. Bu holat forma yuborilguncha yashaydi.
Oddiy formada — React'ning o'zi yetadi: form action va FormData (uncontrolled) yoki useState (controlled, Formalar I). Murakkab formada — ko'p maydon, maydonlararo tekshiruv, dinamik qatorlar, ko'p qadam — forma kutubxonasi: React Hook Form.
import { useState } from "react";
export default function App() {
const [done, setDone] = useState(null);
function book(formData) {
setDone(`${formData.get("name")}, stol band qilindi`);
}
if (done) return <p>{done}</p>;
return (
<form action={book}>
<label>
Ism <input name="name" />
</label>{" "}
<button>Band qilish</button>
</form>
);
}Ekranda:
Malika, stol band qilindiBu formada React state'i umuman yo'q — qiymatni brauzer o'zi saqlaydi, yuborilganda FormData bilan olinadi. Forma state'ining eng yengil ko'rinishi.
2.7 Global store — uzoq komponentlar va React'dan tashqarisi
Oxirgi tur — client state (mijoz holati), ya'ni serverga tegishli emas, lekin:
- ilovaning uzoq joylari ko'radi (har sahifadagi e'lon, sozlamalar);
- tez-tez o'zgaradi (Context bilan butun daraxt qayta chiziladi);
- yoki uni React'dan tashqaridagi kod ham o'qiydi va yozadi (marshrut action'i, taymer, WebSocket ishlovchisi).
Bunday holatni global store da saqlashadi — React'dan tashqarida yashaydigan obyekt, komponentlar unga obuna bo'ladi. G'oyani kutubxonasiz ko'ramiz. useSyncExternalStore darsidan bilasiz: React tashqi manbaga shu hook bilan obuna bo'ladi. Store — qiymat, subscribe va set:
import { useSyncExternalStore } from "react";
// React'dan TASHQARIDA: oddiy JavaScript obyekt
function createStore(initial) {
let state = initial;
const listeners = new Set();
return {
get: () => state,
set(next) {
state = { ...state, ...next };
listeners.forEach((l) => l());
},
subscribe(listener) {
listeners.add(listener);
return () => listeners.delete(listener);
},
};
}
const appStore = createStore({ notice: "" });
// Komponent emas — oddiy funksiya (marshrut action'i kabi)
async function deleteTask(title) {
await Promise.resolve(); // "server"
appStore.set({ notice: `«${title}» o'chirildi` });
}
function Notice() {
const { notice } = useSyncExternalStore(
appStore.subscribe,
appStore.get,
);
return <p role="status">E'lon: {notice || "yo'q"}</p>;
}
function TaskPage() {
return (
<button onClick={() => deleteTask("Non olish")}>
Vazifani o'chirish
</button>
);
}
export default function App() {
return (
<>
<Notice />
<section>
<TaskPage />
</section>
</>
);
}Ekranda:
E'lon: «Non olish» o'chirildi
Vazifani o'chirishdeleteTask — komponent emas, hook chaqira olmaydi, Context'ni ko'rmaydi. Lekin appStore.set ni chaqira oldi va Notice darhol yangilandi. Ular orasida props ham, Provider ham yo'q.
Bu 20 qatorlik store — keyingi darsdagi Zustand ning yuragi. Zustand bunga qulay API, selektorlar (komponent faqat o'zi o'qigan qism o'zgarsa chiziladi) va middleware (persist — localStorage'ga saqlash) qo'shadi. Redux, Jotai, Valtio, MobX — boshqa shakldagi o'sha g'oya (Redux Toolkit I, Atom va proxy state darslarida).
Diqqat: Global store eng kuchli va eng xavfli vosita. Hamma narsani store'ga solsangiz, u "global o'zgaruvchilar qutisi" ga aylanadi: kim, qachon, nimani o'zgartirgani noma'lum. Server ma'lumotini store'ga nusxalash — «Server keshi» bo'limidagi xato, faqat kattaroq. Store'ga faqat boshqa joyga sig'maganlar tushadi.
Tekshirib ko'ring: Context ham uzoq komponentlarga yetkazadi. Global store misolida nega Context yetmasdi?
Javob
Yozuvchi — deleteTask — komponent emas. Context'ga faqat komponent ichida useContext bilan yetiladi. Oddiy funksiya, marshrut action'i yoki taymer Context'ning setState ini ola olmaydi (uni qandaydir yo'l bilan tashqariga "chiqarish" kerak bo'lardi — bu allaqachon store). Tashqi store esa oddiy JavaScript obyekt: istalgan joydan set qilinadi.
3. Hisoblanadigan qiymat — state emas
Xaritadan oldin bitta muhim istisno. Ko'p state aslida state emas — boshqa state'dan hisoblanadi: jami narx, bajarilganlar soni, filtrlangan ro'yxat, "forma to'g'rimi?". Ularni state'ga yozsangiz, ikki nusxa paydo bo'ladi va ular ajralib ketadi:
import { useState } from "react";
const PRICES = { Osh: 35000, Manti: 30000 };
export default function App() {
const [items, setItems] = useState([]);
// ❌ jami — alohida state (ikkinchi nusxa)
const [total, setTotal] = useState(0);
function add(name) {
setItems([...items, name]);
setTotal(total + PRICES[name]);
}
// ✅ hisoblanadi — doim items bilan mos
const computed = items.reduce((s, n) => s + PRICES[n], 0);
return (
<>
<button onClick={() => add("Osh")}>Osh</button>{" "}
<button onClick={() => add("Manti")}>Manti</button>{" "}
<button onClick={() => setItems([])}>Savatni tozalash</button>
<p>State'dagi jami: {total} so'm</p>
<p>Hisoblangan jami: {computed} so'm</p>
</>
);
}Ekranda:
Osh Manti Savatni tozalash
State'dagi jami: 65000 so'm
Hisoblangan jami: 0 so'mSavat bo'sh, lekin "State'dagi jami" 65 000. «Tozalash» faqat items ni o'zgartirdi — total ni unutdi. Har yangi amal (o'chirish, porsiya o'zgartirish) ikkala joyni yangilashni talab qiladi va bir kun biri unutiladi. Hisoblangan qiymat esa hech qachon adashmaydi.
Qoida: minimal state saqlang, qolganini render'da hisoblang (State strukturasini loyihalash). Hisob og'ir bo'lsa — useMemo (memo, useMemo, useCallback), lekin baribir state emas. useEffect bilan "state'ni boshqa state'ga moslash" — xuddi shu xatoning murakkabroq ko'rinishi (Sizga effekt kerak emas).
4. Qaror daraxti
Yangi state paydo bo'ldi — qayerga qo'yish kerak? Savollar shu tartibda:
flowchart TD
A["Hisoblanadimi?"] -->|"ha"| H["Hisoblang — state emas"]
A -->|"yo'q"| B["Egasi — servermi?"]
B -->|"ha"| Q["Server keshi (TanStack Query)"]
B -->|"yo'q"| C["Ulashish / «Orqaga» kerakmi?"]
C -->|"ha"| U["URL (useSearchParams)"]
C -->|"yo'q"| D["Yozilayotgan formami?"]
D -->|"ha"| F["Forma (action / RHF)"]
D -->|"yo'q"| E["Bitta komponentnikimi?"]
E -->|"ha"| L["useState / useReducer"]
E -->|"yo'q"| G["Uzoq yoki React'dan tashqarida?"]
G -->|"yo'q"| K["Ko'tarish / Context"]
G -->|"ha"| S["Global store (Zustand)"]Tartib muhim. Eng yuqoridagi savollar eng ko'p xatoni oldini oladi: "hisoblanadimi" va "egasi servermi" — ikki nusxa muammosining ikki manbai. Global store — eng oxirida: boshqa hech biri to'g'ri kelmasa.
Xarita jadval ko'rinishida:
| Tur | Misol | Vosita | Qachon emas |
|---|---|---|---|
| local | porsiya, ochiq menyu | useState, useReducer |
boshqa komponent ham kerak |
| ko'tarilgan | savat va hisoblagich | ota useState + props |
3+ qavat chuqur |
| Context | tema, til, foydalanuvchi | createContext |
tez-tez o'zgaradi |
| URL | filtr, sahifa, tab | useSearchParams |
maxfiy yoki katta |
| server keshi | ro'yxat, profil | TanStack Query | client state |
| forma | maydonlar, xatolar | action, RHF | yuborilgandan keyin |
| global store | e'lon, sozlamalar | Zustand | server ma'lumoti |
Tekshirib ko'ring: «Bahor» saytidagi narsalar uchun joy tanlang: (1) tanlangan til (uz/ru), (2) qidiruv natijalari, (3) "yangi taom qo'shish" formasining qiymatlari, (4) mehmon tanlagan taom kategoriyasi.
Javob
- Context (yoki global store) — kam o'zgaradi, hamma joyda kerak. Saqlanishi kerak bo'lsa — localStorage bilan. Ba'zi saytlar tilni URL'da ham saqlaydi (
/ru/menyu) — ulashiladigan bo'lsa. - Server keshi — natijalar serverdan. Qidiruv so'zi esa — URL'da (
?q=osh), kalitning bir qismi. - Forma — yuborilguncha.
- URL —
?kategoriya=issiq: havola bilan ulashsa va «Orqaga» bilan qaytsa bo'ladi.
5. Vazifalar qadami: vazifalar holatining xaritasi (holat 22 boshlanishi)
5.1 Hozirgi holat (holat 18)
Real-time darsidan keyingi vazifalar dagi hamma state va uning joyi. Manba — kanon kodi (App.tsx, server-sinxron.ts, hook'lar va kontekstlar):
| State | Hozir qayerda | Turi |
|---|---|---|
| vazifalar ro'yxati | Query keshi (useVazifalar) |
server keshi |
| aloqa holati ("yuklanmoqda", "xato") | so'rov holatidan hisoblanadi | hisoblanadi |
| ekrandagi ro'yxat (optimistik) | useOptimistic |
hisoblanadi (asosiy + kutilayotgan) |
| filtr, saralash | URL (useUrlTanlovi → useSearchParams) |
URL |
| qidiruv matni | useState (App) |
local |
| tahrirdagi vazifa | useState (App) |
local |
| o'chirish so'rovi (dialog) | useState (App) |
local |
| e'lon (toast) | useElon hook — useState + taymer ref (App) |
local, lekin… |
| tema | TemaKonteksti |
Context |
| kirgan foydalanuvchi | AuthProvider (soxta auth) |
Context |
| internet bor/yo'q | useOnlayn (useSyncExternalStore) |
tashqi manba |
| yangi vazifa qoralamasi | forma + localStorage | forma |
| tarix chuqurligi («Orqaga») | sessionStorage + useTarixQadami |
tashqi manba |
Ko'pchilik o'z joyida — 19-qismning birinchi yarmi shunga xizmat qildi: filtr URL'ga (URL — holat saqlash joyi), ro'yxat Query'ga (TanStack Query I–III darslari), statistika bitta keshda.
5.2 Joyi noto'g'ri bo'lgan ikkitasi
E'lon (toast). Hozir App ning local state'i. Lekin uni boshqa sahifalar ham yozmoqchi: vazifa sahifasida vazifa o'chirilsa, ro'yxatga qaytganda "«…» o'chirildi" chiqishi kerak. O'chirish esa vazifa sahifasining marshrut action'ida — u komponent emas. «Global store» bo'limidagi misolning aynan o'zi.
18-qismda e'lonni "optimistik" qilish kerak bo'lgan edi — forma action'i ichidagi oddiy setState action tugaguncha ekranga chiqmasdi. Kanon izohi (ilova-holati.ts, holat 22) store bu muammoni ham yechishini aytadi: useSyncExternalStore ustidagi store'ga yozuv transition ichida ham darhol chiziladi.
Sozlamalar. Yangi talab: «O'chirishdan oldin so'rash» va «Ixcham ro'yxat» sozlamalari. Ular sozlamalar sahifasida o'zgaradi, ro'yxat sahifasida ishlatiladi va brauzerda saqlanishi kerak. Context ham bo'lardi, lekin o'chirish dialogini marshrut action'i ham tekshiradi — yana React'dan tashqarisi.
5.3 Qaror (kanon)
Kanon qarori: server state — Query, URL — router, forma — React Hook Form, sahifalararo client state — store. Store — Zustand. Sabablar kanonda o'lchandi va sinaldi (ilova-holati.ts izohi, holat 22):
// ilova-holati.ts — mijoz (client) holati: Zustand store
// (19/#21–#22). Server holati — Query'da (server-sinxron.ts);
// bu yerda — serverga tegishli bo'lmagan, lekin BIR NECHTA
// sahifa (va loader/action) ko'radigan narsalar: e'lon (toast)
// va foydalanuvchi sozlamalari.
// Nega Context emas (o'lchandi/sinaldi, QADAMLAR #20–#22):
// 1. Store React'dan TASHQARIDA ham bor: marshrut action'i
// (vazifa o'chirildi → ro'yxat sahifasida e'lon) getState()
// bilan yozadi — Context'ga hook'siz yetib bo'lmaydi
// 2. Selektor: komponent faqat o'zi o'qigan qism o'zgarsa
// chiziladi (e'lon o'zgarsa — faqat Bildirishnoma, App emas)
// 3. useSyncExternalStore ustida: action (transition) ichida
// yozilsa ham darhol chiziladi — 18/#05 dagi "optimistik
// e'lon" endi kerak emasUchala sabab bugungi darsdan: React'dan tashqarida yozish («Global store»), Context'ning "hamma o'qiydigan qayta chiziladi" chegarasi («Context») va useSyncExternalStore. Kod — keyingi ikki darsda, commit — Zustand II da.
Store'ga nima tushmaydi — qaror bilan birga yozilgani muhim:
- vazifalar ro'yxati — Query'da qoladi (server ma'lumotini nusxalamaymiz);
- filtr va saralash — URL'da qoladi;
- qidiruv matni, tahrirdagi vazifa —
Appning local state'i qoladi: ularni boshqa hech kim ko'rmaydi; - tema va foydalanuvchi — Context'da qoladi: kam o'zgaradi, faqat komponentlar o'qiydi.
Formalar: yangi formalar (vazifa tahriri, ko'p qadamli forma) — React Hook Form bilan. Ro'yxat sahifasidagi tezkor forma esa o'zgarmaydi: 159 brauzer tekshiruvining ~40 tasi unga tayanadi — ishlab turgan kodni ko'chirish xavfi foydasidan katta.
5.4 Narxi
Har vosita — to'plamga qo'shimcha. 19-qism oxiridagi taqsimot (gzip, kanon o'lchovi):
- React Router29,4 KB
- RHF + resolvers14,7 KB
- TanStack Query9,9 KB
- zod/mini5,8 KB
- Zustand1,3 KB
Manba: sinov/19/QADAMLAR.md «Yakuniy holat» va hisobot/19-qarorlar.md (vite build, gzip -9)
Birinchi yuklash 84.6 KB dan ~153.7 KB gacha o'sadi — deyarli ikki baravar. Lighthouse Performance 96 dan 93–96 ga, FCP 1.5 s dan 2.0 s ga tushdi. Bu yashirilmaydi. Evaziga — har state o'z joyida va o'sha joy uchun sinalgan vosita. Zustand'ning +1.3 KB i bu ro'yxatdagi eng arzoni: store g'oyasi kichik («Global store» bo'limidagi 20 qator).
Bu darsda kod o'zgarmaydi. Branch — keyingi darsda: feat/zustand.
6. Ko'p uchraydigan xatolar
| Belgi | Sabab | Davosi |
|---|---|---|
| ikki joyda ikki xil son | hisoblanadigan qiymat state'da | render'da hisoblang |
| prop o'zgardi, ekran eski | server ma'lumoti useState ga nusxalangan |
keshdan to'g'ridan o'qing |
| havola ulashilganda filtr yo'qoladi | filtr useState da |
URL |
| "Orqaga" bosilganda sahifadan chiqib ketadi | har harf URL'ga yozildi (tarix to'ldi) | replace yoki debounce |
| bitta harf yozilganda butun ilova chiziladi | tez o'zgaradigan state Context'da | local yoki store + selektor |
| store'da server ro'yxati eskiradi | server ma'lumoti store'ga nusxalangan | Query |
| action'dan e'lon chiqmaydi | e'lon komponent state'ida | tashqi store |
| props 5 qavat o'tadi | ko'tarish haddan oshgan | Context yoki kompozitsiya |
Oxirgisida eslatma: props drilling'ning birinchi davosi ko'pincha store emas, kompozitsiya — komponentni children bilan uzatish (Kompozitsiya orqali optimallashtirish).
7. Mashqlar
Mashqlar kurs/mashqlar/19/20-state-turlari/ da.
1-mashq (oson): joy tanlang
«Bahor» yetkazib berish ilovasi. Har state uchun turini yozing: local, URL, server, forma, store yoki hisoblanadi.
- Buyurtmalar tarixi →
- Savatdagi jami summa (savat bor) →
- Manzil kiritish maydonlari (yuborilguncha) →
- Menyuda tanlangan saralash ("arzonidan") →
- "Kuryer yo'lda" bildirishnomasi — WebSocket ishlovchisi yozadi, hamma sahifada ko'rinadi →
- Taom rasmi ustidagi "kattalashtirish" oynasi ochiqmi →
Yechim
- server — egasi server.
- hisoblanadi — savatdan (
reduce), alohida saqlanmaydi. - forma — yuborilguncha yashaydi.
- URL — ulashsa bo'ladi.
- store — WebSocket ishlovchisi komponent emas, ko'ruvchilar — uzoq.
- local — faqat shu rasm komponenti.
2-mashq (o'rta): nusxani olib tashlang
Komponent server ma'lumotini useState ga nusxalagan va «Yangilash» dan keyin eski reytingni ko'rsatadi. Nusxani olib tashlang: reyting to'g'ridan keshdan chiqsin.
import { useState } from "react";
import {
QueryClient,
QueryClientProvider,
useQuery,
} from "@tanstack/react-query";
const queryClient = new QueryClient();
let rating = 4.5;
function fetchRating() {
const value = rating;
rating += 0.1;
return Promise.resolve(Number(value.toFixed(1)));
}
function RatingView({ initial }) {
const [value] = useState(initial);
return <p>Reyting: {value}</p>;
}
function Dish() {
const { data, refetch } = useQuery({
queryKey: ["rating"],
queryFn: fetchRating,
});
if (data === undefined) return <p>Yuklanmoqda…</p>;
return (
<>
<RatingView initial={data} />
<button onClick={() => refetch()}>Yangilash</button>
</>
);
}
export default function App() {
return (
<QueryClientProvider client={queryClient}>
<Dish />
</QueryClientProvider>
);
}Ekranda:
Reyting: 4.5
YangilashYechim
import {
QueryClient,
QueryClientProvider,
useQuery,
} from "@tanstack/react-query";
const queryClient = new QueryClient();
let rating = 4.5;
function fetchRating() {
const value = rating;
rating += 0.1;
return Promise.resolve(Number(value.toFixed(1)));
}
function RatingView({ value }) {
return <p>Reyting: {value}</p>;
}
function Dish() {
const { data, refetch } = useQuery({
queryKey: ["rating"],
queryFn: fetchRating,
});
if (data === undefined) return <p>Yuklanmoqda…</p>;
return (
<>
<RatingView value={data} />
<button onClick={() => refetch()}>Yangilash</button>
</>
);
}
export default function App() {
return (
<QueryClientProvider client={queryClient}>
<Dish />
</QueryClientProvider>
);
}Ekranda:
Reyting: 4.6
YangilashRatingView endi state'siz — u faqat ko'rsatadi. Prop'ning nomi ham o'zgardi: initial ("boshlang'ich") nomi "faqat birinchi qiymat ishlatiladi" degan va'da edi. Haqiqatan boshlang'ich qiymat kerak bo'lsa (forma), nomida shuni aytish — yaxshi odat.
3-mashq (qiyin): kichik store
«Global store» bo'limidagi createStore ni ishlating. «Bahor» oshxonasi:
- Store:
{ ready: 0 }— tayyor buyurtmalar soni. markReady()— oddiy funksiya (komponent emas), soni +1. UsetTimeoutichidan chaqiriladi — oshxonadagi "taymer".- Ikki komponent:
Counter("Tayyor: N ta") vaWaiter("Ofitsiant: N ta olib ketsin") — ikkalasi store'danuseSyncExternalStorebilan. - Ilova ochilganda 2 ta taymer: 100 ms va 200 ms da
markReady().
Ishora: store.set({ ready: store.get().ready + 1 }). Taymerlarni modul darajasida ishga tushirsangiz bo'ladi.
Yechim
import { useSyncExternalStore } from "react";
function createStore(initial) {
let state = initial;
const listeners = new Set();
return {
get: () => state,
set(next) {
state = { ...state, ...next };
listeners.forEach((l) => l());
},
subscribe(listener) {
listeners.add(listener);
return () => listeners.delete(listener);
},
};
}
const kitchen = createStore({ ready: 0 });
function markReady() {
kitchen.set({ ready: kitchen.get().ready + 1 });
}
setTimeout(markReady, 100);
setTimeout(markReady, 200);
function useReady() {
return useSyncExternalStore(kitchen.subscribe, kitchen.get).ready;
}
function Counter() {
return <p>Tayyor: {useReady()} ta</p>;
}
function Waiter() {
return <p>Ofitsiant: {useReady()} ta olib ketsin</p>;
}
export default function App() {
return (
<>
<Counter />
<section>
<Waiter />
</section>
</>
);
}Ekranda:
Tayyor: 2 ta
Ofitsiant: 2 ta olib ketsinmarkReady React haqida hech narsa bilmaydi — u oddiy taymerdan chaqirildi. Ikki komponent bir-birini bilmaydi va umumiy otasidan props olmaydi. useReady — store'ni o'qiydigan kichik custom hook: Zustand ham aynan shunday hook beradi (useStore), faqat selektor bilan.
4-mashq: Vazifalar qadami
O'z vazifalar ingizdagi state'lar jadvalini tuzing (App.tsx, hooklar/, kontekst/, server-sinxron.ts ni o'qib). Har biriga: hozir qayerda, qaror daraxti bo'yicha qayerda bo'lishi kerak. Kanon jadvali bilan solishtiring.
Yechim
Kanon jadvali — «Hozirgi holat» bo'limida. Farq faqat ikki qatorda bo'lishi kerak: e'lon va (yangi) sozlamalar — store. Agar sizda boshqa farq chiqsa (masalan, filtr hali useState da) — tegishli darsga qayting: URL — holat saqlash joyi, TanStack Query I. Kod o'zgarishi — keyingi darsda.
8. Real ishda
- Ko'p loyihalarning "state boshqaruvi" muammosi aslida server state muammosi. TanStack Query kiritilgach, Redux store'ining katta qismi o'chib ketadi — bu sohada ko'p aytiladigan tajriba. Qolgan mijoz holati ko'pincha kichik.
- Kod review'da eng ko'p yoziladigan izohlardan biri: "bu state'dan hisoblanadi,
useStatekerak emas" va "props'dan state'ga nusxa — eskiradi". - Arxitektura hujjatlarida (ADR) state xaritasi alohida bo'lim bo'ladi: qaysi tur qaysi vositada. Yangi dasturchi uchun eng foydali sahifa.
- Next.js da yana bir joy qo'shiladi — server komponent: ma'lumot serverda olinadi va klientga umuman state bo'lib kelmaydi (Next.js qismida).
- Intervyu savollari: "state'ni qayerda saqlashni qanday hal qilasiz?", "Context va Redux/Zustand farqi?", "server state va client state?", "derived state nima va nega uni saqlamaslik kerak?".
Xulosa
- Yetti joy: local, ko'tarilgan, Context, URL, server keshi, forma, global store. Har birining o'z vazifasi va o'z chegarasi bor.
- Hisoblanadigan qiymat — state emas. Server ma'lumotini nusxalamang — keshdan o'qing.
- Qaror tartibi: hisoblanadimi → egasi servermi → ulashiladimi → formami → bitta komponentnikimi → uzoqmi yoki React'dan tashqaridami.
- Context — kam o'zgaradigan narsani uzatish uchun; React'dan tashqarida yozish kerak bo'lsa — store.
vazifalar: deyarli hammasi joyida; e'lon va sozlamalar — Zustand store'ga (keyingi ikki dars), formalar — RHF'ga (React Hook Form I dan).
Keyingi dars: Zustand I: kichik global store — create, set/get, selektorlar va store'ni komponentdan tashqarida ishlatish; vazifalar dagi e'lon store'ga ko'chadi.
Manbalar
- React: Choosing the State Structure, Sharing State Between Components — react.dev/learn/choosing-the-state-structure, …/sharing-state-between-components
- React: You Might Not Need an Effect — react.dev/learn/you-might-not-need-an-effect
- React:
useSyncExternalStore— react.dev/reference/react/useSyncExternalStore - TkDodo: React Query as a State Manager — tkdodo.eu/blog/react-query-as-a-state-manager
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!