IlmHamroh
JavaScript Full-stack/19-qism. React: routing, server state, formalar20/36-dars21 daqiqa
Mundarija (26)

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 useState ga nusxalash nega xato ekanini jonli misolda ko'rasiz.
  • Qaror daraxti bilan har yangi state uchun joy tanlaysiz.
  • React'dan tashqaridagi store g'oyasini useSyncExternalStore bilan ko'rasiz — keyingi darsdagi Zustand'ning yuragi.
  • vazifalar dagi 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:

jsx
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:

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

jsx
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:

text
Savatda: 2 ta
Osh qo'shish Manti qo'shish

Ko'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.

jsx
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:

text
Tungi rejim
Tugma rejimi: tungi

Layout 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:

jsx
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:

text
Manzil: /?bolim=ichimliklar
Ichimliklar
Ko'k choy, Kompot

useState 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:

jsx
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:

text
Keshdan: 37000 so'm
Nusxa: 35000 so'm
Yangilash

Server 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.

jsx
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:

text
Malika, stol band qilindi

Bu 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:

jsx
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:

text
E'lon: «Non olish» o'chirildi
Vazifani o'chirish

deleteTask — 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:

jsx
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:

text
Osh Manti Savatni tozalash
State'dagi jami: 65000 so'm
Hisoblangan jami: 0 so'm

Savat 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
  1. 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.
  2. Server keshi — natijalar serverdan. Qidiruv so'zi esa — URL'da (?q=osh), kalitning bir qismi.
  3. Forma — yuborilguncha.
  4. 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):

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

Uchala 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 — App ning 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):

19-qism: state vositalarining birinchi yuklashdagi narxi (gzip)
  • 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
  1. server — egasi server.
  2. hisoblanadi — savatdan (reduce), alohida saqlanmaydi.
  3. forma — yuborilguncha yashaydi.
  4. URL — ulashsa bo'ladi.
  5. store — WebSocket ishlovchisi komponent emas, ko'ruvchilar — uzoq.
  6. 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.

jsx
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:

text
Reyting: 4.5
Yangilash
Yechim
jsx
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:

text
Reyting: 4.6
Yangilash

RatingView 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:

  1. Store: { ready: 0 } — tayyor buyurtmalar soni.
  2. markReady() — oddiy funksiya (komponent emas), soni +1. U setTimeout ichidan chaqiriladi — oshxonadagi "taymer".
  3. Ikki komponent: Counter ("Tayyor: N ta") va Waiter ("Ofitsiant: N ta olib ketsin") — ikkalasi store'dan useSyncExternalStore bilan.
  4. 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
jsx
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:

text
Tayyor: 2 ta
Ofitsiant: 2 ta olib ketsin

markReady 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, useState kerak 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
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
React state turlari: local, URL, server keshi, forma va global store — qaysi biri qayerda — IlmHamroh