IlmHamroh
JavaScript Full-stack/17-qism. React asoslari12/45-dars20 daqiqa
Mundarija (28)

State I: useState — React komponentining xotirasi va qayta render

Qisqacha: State — komponentning chizishlar orasida saqlanadigan xotirasi. const [count, setCount] = useState(0) — count joriy qiymat, setCount uni o'zgartiradi va React'ga "qayta chiz" deydi. React komponent funksiyasini qayta chaqiradi, useState esa endi yangi qiymatni qaytaradi. Oddiy let bunday ishlamaydi: u har chaqiruvda noldan boshlanadi va o'zgarsa, React buni bilmaydi. Har komponent nusxasining state'i o'ziniki. Boshlang'ich qiymat faqat birinchi chizishda ishlatiladi.

Bu darsda

  • Nega oddiy o'zgaruvchi ekranni yangilamasligini ikki sabab bilan tushuntira olasiz.
  • useState bilan state e'lon qilasiz, uni o'qiysiz va setter bilan o'zgartirasiz.
  • Setter chaqirilgandan keyin nima bo'lishini qadamma-qadam (render jadvali bilan) ayta olasiz.
  • Bir komponentda bir nechta state ishlatasiz va har nusxaning o'z state'i borligini ko'rasiz.
  • State'ga TypeScript turi berasiz va boshlang'ich qiymat tuzoqlarini taniysiz.

Oldin bilishingiz kerak: Hodisalarni boshqarish, Massiv destructuring, Holatdan UI chizish: state → render.

1. Nega bu kerak?

Oldingi darslarda ekran o'zgarishi uchun draw() funksiyasini o'zimiz chaqirdik: vanilla o'zgaruvchi yangilandi → root.render(...) qaytadan. Bu JSX II dan beri ishlatgan "ko'prik". U ishlaydi, lekin u v6 dagi render() ning o'zi. Holat komponentdan tashqarida, uni yangilash va chizishni eslab qolish — sizning vazifangiz.

React'ning va'dasi boshqa edi: UI = f(state). Holat o'zgaradi — ekran o'zi ergashadi. Buning uchun komponent o'z xotirasiga ega bo'lishi kerak. Bu xotira React'ga "meni kuzat" deb aytishi ham kerak. Bunday xotira — state, uni beradigan funksiya — useState.

Hayotiy o'xshatish: ofitsiantning bloknoti. Mijoz "yana bitta choy" desa, ofitsiant bloknotdagi raqamni o'zgartiradi va oshxonaga xabar beradi. Raqamni boshida saqlamasa — har safar noldan boshlaydi. Xabar bermasa — oshxona bilmaydi. useState ikkalasini ham qiladi: eslab qoladi va xabar beradi.

2. Oddiy o'zgaruvchi nega ishlamaydi

React nima va nega darsida bu xatoni bir marta ko'rgandik. Endi uni ichidan ko'ramiz — console.log bilan:

jsx
function App() {
  let cups = 0; // ❌ oddiy o'zgaruvchi
  console.log("App chaqirildi, cups =", cups);

  function handleClick() {
    cups = cups + 1;
    console.log("bosildi, cups =", cups);
  }

  return (
    <>
      <p>Ko'k choy: {cups} ta</p>
      <button onClick={handleClick}>Choy +</button>
    </>
  );
}

Konsolda:

text
App chaqirildi, cups = 0
bosildi, cups = 1
bosildi, cups = 2

Ekranda:

text
Ko'k choy: 0 ta
Choy +

cups haqiqatan 2 bo'ldi — konsol buni ko'rsatyapti. Ekranda esa 0. Ikki sabab bor:

  1. React bilmaydi. O'zgaruvchiga yangi qiymat yozish React'ga hech narsa demaydi. "App chaqirildi" faqat bir marta chiqdi — React komponentni qayta chaqirmadi.
  2. Qayta chaqirsa ham, yo'qoladi. let cups = 0 — funksiyaning lokal o'zgaruvchisi. Funksiya har chaqiruvda uni yana 0 bilan boshlaydi (Scope asoslari). Xotira chaqiruvlar orasida yashamaydi.

Bizga ikkala muammoni yechadigan narsa kerak: chaqiruvlar orasida saqlanadigan qiymat va uni o'zgartirganda React'ga xabar.

3. useState

3.1 Birinchi state

jsx
import { useState } from "react";

function App() {
  const [cups, setCups] = useState(0);
  console.log("App chaqirildi, cups =", cups);

  function handleClick() {
    setCups(cups + 1);
  }

  return (
    <>
      <p>Ko'k choy: {cups} ta</p>
      <button onClick={handleClick}>Choy +</button>
    </>
  );
}

Konsolda:

text
App chaqirildi, cups = 0
App chaqirildi, cups = 1
App chaqirildi, cups = 2

Ekranda:

text
Ko'k choy: 2 ta
Choy +

Endi "App chaqirildi" uch marta chiqdi: birinchi chizish va ikki bosish. Har safar cups yangi qiymat bilan keldi. Qatorma-qator:

  • import { useState } from "react" — useState React'ning funksiyasi. use bilan boshlanadigan bunday funksiyalar hook deyiladi: ular komponentni React'ning ichki imkoniyatlariga "ilib" ulaydi.
  • useState(0) — "menga xotira katagi ber, boshlanishi — 0". U ikki elementli massiv qaytaradi.
  • const [cups, setCups] = … — massiv destructuring: birinchisi — joriy qiymat, ikkinchisi — uni o'zgartiruvchi funksiya (setter). Nomlash odati: [narsa, setNarsa].
  • setCups(cups + 1) — yangi qiymatni beradi va React'ga "qayta chiz" deydi.

3.2 Setter chaqirilganda nima bo'ladi

Bosishdan ekrangacha bo'lgan yo'lni qadamma-qadam yozamiz:

Qadam State Ekranda
birinchi chizish: useState(0) → [0, setCups] cups: 0 Ko'k choy: 0 ta
bosildi: setCups(0 + 1) — React navbatga yozadi cups: 0 (hali) Ko'k choy: 0 ta
React App ni qayta chaqiradi: useState(0) → [1, setCups] cups: 1 Ko'k choy: 1 ta
bosildi: setCups(1 + 1), qayta chaqiruv cups: 2 Ko'k choy: 2 ta

Ikkinchi qatorga e'tibor bering: setter cups o'zgaruvchisini o'zgartirmaydi. U React'ga "keyingi chizishda 1 bo'lsin" deydi. Qiymat React ichida saqlanadi, komponentdan tashqarida. Qayta chaqirilgan App da useState(0) qatori yana bajariladi, lekin bu safar React boshlang'ich 0 ga qaramaydi. U saqlab qo'ygan 1 ni qaytaradi. Bu nozik joyni keyingi darsda chuqur ko'ramiz.

sequenceDiagram
  participant U as Foydalanuvchi
  participant K as App komponenti
  participant R as React
  U->>K: «Choy +» bosildi
  K->>R: setCups(1)
  R->>R: qiymatni saqlaydi, chizishni rejalashtiradi
  R->>K: App() ni qayta chaqiradi
  K->>R: useState → 1, yangi JSX
  R->>U: DOM'da faqat "0" → "1"

Diagrammada uch ishtirokchi: foydalanuvchi, komponent va React. Komponent React'ga faqat so'rov yuboradi. Qayta chaqirish va DOM'ni yangilash — React'ning ishi. DOM'da faqat raqam o'zgaradi: <p> va tugma o'sha-o'sha qoladi (React nima va nega dagi "faqat farqini").

Tekshirib ko'ring: setCups(cups + 1) dan keyingi qatorda console.log(cups) yozsak, nima chiqadi — eski qiymatmi, yangimi?

Javob

Eski. cups — shu chaqiruvning const o'zgaruvchisi, setter uni o'zgartira olmaydi. Yangi qiymat faqat keyingi chizishda, App qayta chaqirilganda keladi. Bu — keyingi darsning asosiy g'oyasi: snapshot.

4. Bir nechta state

Komponentda istalgancha useState bo'lishi mumkin. Har biri — alohida katak:

jsx
import { useState } from "react";

function App() {
  const [name, setName] = useState("");
  const [guests, setGuests] = useState(1);
  const total = guests * 35000; // state emas — hisoblanadi

  return (
    <>
      <input
        aria-label="Ism"
        value={name}
        onChange={(event) => setName(event.target.value)}
      />
      <button onClick={() => setGuests(guests + 1)}>Mehmon +</button>
      <p>
        Bron: {name === "" ? "ism yo'q" : name}, {guests} kishi
      </p>
      <p>Osh uchun: {total} so'm</p>
    </>
  );
}

Ekranda:

text
Mehmon +
Bron: Malika, 3 kishi
Osh uchun: 105000 so'm

Uch narsa:

  • name — satr, guests — son. State istalgan qiymat bo'la oladi: satr, son, boolean, massiv, obyekt. Obyekt va massiv bilan ishlashning o'z qoidalari bor — State III darsida.
  • total — state emas. U guests dan har chizishda hisoblanadi. React nima va nega dagi qoida: hisoblab bo'ladigan narsani state'da saqlamang. Aks holda ikkalasini birga yangilashni unutish mumkin (State strukturasini loyihalash).
  • value={name} va onChange — maydon matni state'dan keladi va har harfda state'ga qaytadi. Bunday maydon controlled (boshqariladigan) deyiladi. Hodisalar darsidagi katak ham shunday edi. Formalardagi tafsilotlar — Formalar I darsida.

5. Har nusxaning o'z state'i

Komponentni bir necha marta ishlatsangiz, har nusxa o'z xotirasini oladi:

jsx
import { useState } from "react";

function DishCounter({ name }) {
  const [count, setCount] = useState(0);
  return (
    <li>
      {name}: {count}{" "}
      <button
        aria-label={`${name} +`}
        onClick={() => setCount(count + 1)}
      >
        +
      </button>
    </li>
  );
}

function App() {
  return (
    <ul>
      <DishCounter name="Osh" />
      <DishCounter name="Manti" />
      <DishCounter name="Choy" />
    </ul>
  );
}

Ekranda:

text
Osh: 2 +
Manti: 0 +
Choy: 1 +

Funksiya bitta, xotira uchta. React state'ni funksiyaga emas, daraxtdagi o'ringa bog'laydi: "<ul> ning birinchi bolasi — DishCounter, uning state'i — 2". Shuning uchun Osh, Manti va Choy bir-biriga xalal bermaydi. O'rin o'zgarsa yoki key o'zgarsa nima bo'lishi — State'ning saqlanishi va key bilan reset darsida.

State shaxsiy: App uchta count ning birortasini ham o'qiy olmaydi. "Jami nechta taom" kerak bo'lsa — state'ni otaga ko'tarish kerak (State'ni ko'tarish).

6. Boshlang'ich qiymat

6.1 Faqat birinchi chizishda

useState(start) dagi qiymat faqat birinchi chizishda ishlatiladi. Keyin React o'zining saqlaganini qaytaradi. Bu props'dan state yasaganda tuzoqqa olib keladi:

jsx
import { useState } from "react";
import { createRoot } from "react-dom/client";

const root = createRoot(document.getElementById("root"));
let start = 1;

function Counter({ start }) {
  const [count, setCount] = useState(start); // faqat birinchi marta
  return (
    <p>
      start={start}, count={count}
    </p>
  );
}

function draw() {
  root.render(
    <>
      <Counter start={start} />
      <button onClick={() => { start = start + 10; draw(); }}>
        Keyingi
      </button>
    </>
  );
}
draw();

Ekranda:

text
start=21, count=1
Keyingi

Ota start ni ikki marta o'zgartirdi — props 21. State esa birinchi qiymatda — 1 — qotib qoldi. Bu xato emas, ataylab shunday: state — komponentning o'z xotirasi, props esa faqat "boshlanishi". Props bilan doim mos bo'lishi kerak bo'lsa, state umuman kerak emas — props'ning o'zini ishlating. Bu holatlar — State strukturasini loyihalash darsida.

6.2 Lazy init: og'ir hisob bir marta

Boshlang'ich qiymatni hisoblash qimmat bo'lsa-chi? Masalan, localStorage dan o'qish yoki katta massivni qayta ishlash:

jsx
import { useState } from "react";

function readSaved(label) {
  console.log(label, "— o'qildi");
  return 0;
}

function App() {
  // A: readSaved har chizishda chaqiriladi
  const [a, setA] = useState(readSaved("A"));
  // B: funksiyaning o'zi — React faqat birinchi chizishda chaqiradi
  const [b, setB] = useState(() => readSaved("B"));
  return (
    <button onClick={() => { setA(a + 1); setB(b + 1); }}>+</button>
  );
}

Konsolda:

text
A — o'qildi
B — o'qildi
A — o'qildi
A — o'qildi

Ekranda:

text
+

useState(readSaved("A")) — JavaScript argumentni avval hisoblaydi. Shuning uchun readSaved har chizishda chaqiriladi, natijasi esa birinchidan keyin tashlab yuboriladi. useState(() => readSaved("B")) da esa funksiyaning o'zi beriladi (Hodisalar dagi f va f() farqi). React uni faqat birinchi chizishda chaqiradi. Bu lazy init (dangasa boshlash). vazifalar da tema tanlovi aynan shunday localStorage dan o'qiladi (Mini-ilova darsida).

7. TypeScript va state

TypeScript turni boshlang'ich qiymatdan chiqaradi: useState(0) — number, useState("") — string. Tuzoq — null bilan boshlash:

tsx
import { useState } from "react";

export function App() {
  const [name, setName] = useState("");
  const [error, setError] = useState(null);

  function check() {
    setName(5);
    setError("Ism kiritilmagan");
  }

  return (
    <p onClick={check}>
      {name} {error}
    </p>
  );
}

Xato:

text
TS2345: Argument of type 'number' is not assignable to parameter of type 'SetStateAction<string>'.
TS2345: Argument of type '"Ism kiritilmagan"' is not assignable to parameter of type 'SetStateAction<null>'.

Birinchisi — kutilgan himoya: name satr, son yozib bo'lmaydi. Ikkinchisi — tuzoq. useState(null) dan TypeScript turni null deb oldi: "bu katakda faqat null bo'ladi". Xato matni hech qachon kiritib bo'lmaydi. Yechim — turni aniq yozish, generic bilan (Generics):

tsx
import { useState } from "react";

export default function App() {
  const [error, setError] = useState<string | null>(null);
  return (
    <>
      <button onClick={() => setError("Ism kiritilmagan")}>
        Tekshirish
      </button>
      <p role="status">{error}</p>
    </>
  );
}

Ekranda:

text
Tekshirish

useState<string | null>(null) — "bu katakda satr yoki null, boshlanishi null". vazifalar dagi forma xatosi aynan shunday e'lon qilinadi. SetStateAction<string> — setter qabul qiladigan narsa: yangi qiymat yoki funksiya (keyingi dars dagi updater).

8. Ikki qoida va bitta xato

8.1 Hooklar qoidasi

useState ni faqat komponentning yuqori darajasida chaqiring — if, sikl yoki ichki funksiya ichida emas. React state kataklarini chaqiruv tartibi bo'yicha taniydi: "birinchi useState — name, ikkinchisi — guests". Shart ichida bo'lsa, tartib chizishdan chizishga o'zgaradi va kataklar almashib ketadi. Linter buni ushlaydi (vazifalar dagi react-hooks/rules-of-hooks). Mana haqiqiy chiqish: useState erta return dan keyin yozilgan fayl:

text
  7:27  error  React Hook "useState" is called conditionally. React Hooks must be called in the exact same order in every component render. Did you accidentally call a React Hook after an early return?  react-hooks/rules-of-hooks

"useState shartli chaqirilyapti. Hooklar har chizishda aynan bir xil tartibda chaqirilishi kerak. Erta return dan keyin chaqirib qo'ymadingizmi?" Nega aynan shunday — Hooklar qoidalari darsida. TypeScript bu xatoni ko'rmaydi — faqat linter.

8.2 Render paytida setter: cheksiz sikl

Hodisalar darsida va'da qilingan xato:

jsx
import { useState } from "react";

function App() {
  const [count, setCount] = useState(0);
  return <button onClick={setCount(count + 1)}>{count}</button>;
}

Konsolda:

text
Uncaught Error: Too many re-renders. React limits the number of renders to prevent an infinite loop.

"Juda ko'p qayta chizish. React cheksiz sikl bo'lmasligi uchun chizishlar sonini cheklaydi." setCount(count + 1) chizish paytida chaqirildi → React qayta chizishni rejalashtirdi → qayta chizishda yana setCount → … React bir necha o'n marta urinib, to'xtatdi. Saytdagi xato qutisida Maslahat ham bor: onClick={() => setSon(son + 1)} deb yozing. Tuzatish — arrow funksiya.

9. Vazifalar qadami: forma xotirasi — useState

9.1 Reja

Ro'yxat React'da (Hodisalar). Endi navbat — "Yangi vazifa" formasi. Uch dars davomida bitta branch:

bash
git switch -c refactor/forma-usestate

Formada nimani eslab qolish kerak? v6 da "xato bormi" degan savolga DOM javob berardi: maydon.getAttribute("aria-invalid") !== "true". Teg takliflari ham DOM'da yashardi: tegTakliflari.hidden, replaceChildren(...tugmalar). Endi ikkalasi state bo'ladi, atributlar esa state'dan chiziladi.

9.2 YangiVazifaForma.tsx

src/YangiVazifaForma.tsx — asosiy qismlari (to'liq fayl 162 qator). Uzun izoh qatorlari telefon uchun qayta bo'lingan:

tsx
// YangiVazifaForma.tsx — yangi vazifa: maydon, xato va teg
// takliflari (17/#12: useState). Forma o'zi eslab qoladigan ikki
// narsa — xato matni va takliflar — komponentning state'i:
// o'zgarsa, React formani qayta chizadi. v6 da "xato bormi"
// savoliga DOM javob berardi (aria-invalid atributi), endi — state,
// atribut esa undan chiziladi
import { useState } from "react";
…
export function YangiVazifaForma({
  boshlangichMatn,
  xatoniTop,
  takliflarniTop,
  onYoz,
  onQosh,
}: Props) {
  // useState(boshlang'ich) → [joriy qiymat, uni almashtiruvchi
  // funksiya]
  const [xato, setXato] = useState<string | null>(null);
  const [takliflar, setTakliflar] = useState<readonly string[]>([]);

Ikki state, ikkalasi ham «TypeScript va state» bo'limidagi qoida bilan. xato — string | null, takliflar — faqat o'qiladigan satrlar massivi. Ular qayerda o'zgaradi:

tsx
  function yuborildi(event: FormEvent<HTMLFormElement>) {
    event.preventDefault(); // sahifa qayta yuklanmasin
    const maydon = maydonniOl(event.currentTarget);
    if (maydon === null) {
      return;
    }
    const natija = onQosh(maydon.value.trim());
    switch (natija.tur) {
      case "xato":
        setXato(natija.xabar);
        maydon.focus();
        return;
      case "saqlanmadi":
        return; // e'lonni asosiy.ts qildi, matn maydonda qoladi
      case "qoshildi":
        setXato(null);
        setTakliflar([]);
        // form.reset() EMAS: React defaultValue ni value
        // atributiga yozadi va reset() maydonga qoralamani
        // qaytarardi (o'lchandi)
        maydon.value = "";
        return;
      default: {
        const qolmadi: never = natija;
        void qolmadi;
      }
    }
  }

onQosh — ota bergan funksiya. U QoshishNatijasi qaytaradi — Shartli render darsidagi discriminated union naqshi, never bilan. src/turlar.ts ga qo'shiladi:

ts
export type QoshishNatijasi =
  | { readonly tur: "qoshildi" } // maydon tozalanadi
  // internet yo'q — matn maydonda qoladi:
  | { readonly tur: "saqlanmadi" }
  | { readonly tur: "xato"; readonly xabar: string };

Forma natijaga qarab o'z state'ini yangilaydi. setXato(natija.xabar) — xato matni paydo bo'ladi. Endi u chizishda qanday ishlatiladi:

tsx
        <input
          id="yangi-matn"
          name="matn"
          required
          maxLength={Vazifa.MAKS_UZUNLIK}
          autoComplete="off"
          defaultValue={boshlangichMatn}
          aria-invalid={xato !== null ? true : undefined}
          aria-describedby={xato !== null ? XATO_ID : undefined}
          onInput={(event) => ozgardi(event.currentTarget)}
          onKeyDown={maydondaTugma}
        />
 …
      <p id={XATO_ID} className="xato" role="status">
        {xato}
      </p>

v6 dagi xatoniKorsat() va xatoniTozala() — har biri uch qator DOM buyrug'i — yo'qoldi. Ularning o'rnida ikki atribut va bitta {xato}, uchalasi ham state'dan. aria-invalid ga false emas, undefined — Shartli render dagi qoida. Malika'ning ekran o'quvchisi role="status" paragrafidagi xatoni o'qib beradi.

Teg takliflari ham state'dan chiziladi:

tsx
      <div
        id="teg-takliflari"
        role="group"
        aria-label="Teg takliflari"
        hidden={takliflar.length === 0}
        onKeyDown={takliflardaTugma}
      >
        {takliflar.map((teg) => (
          <button
            key={teg}
            type="button"
            data-teg={teg}
            onClick={(event) => tegniQoy(teg, event.currentTarget.form)}
          >
            {teg}
          </button>
        ))}
      </div>

v6 dagi replaceChildren(...takliflar.map(…createElement("button")…)) — endi map va key={teg} (Ro'yxatlar va key): teg nomlari takrorlanmaydi.

Maydonning o'zi esa uncontrolled — defaultValue, value emas. Matn DOM'da turadi, forma uni FormData siz, maydon.value bilan o'qiydi. Nega bu yerda controlled emas, va form.reset() nega ishlatilmagani — izohda "o'lchandi" deyilgan. Kanonda sinaldi: React defaultValue ni value atributiga ham yozadi, reset() esa maydonni atributdagi qiymatga — qoralamaga — qaytaradi. Tafsilotlari Formalar II darsida.

9.3 Komponent testlari

Forma hali sahifaga ulanmagan — uni asosiy.ts va yangi holat bilan birga State III darsida ulaymiz. Lekin komponentni hozirdanoq sinash mumkin: test unga props'ni o'zi beradi. Yangi fayl tekshiruv/YangiVazifaForma.test.tsx da oltita test bor. Ulardan biri — state'ning asosiy ishi:

tsx
  test("xato: matni ko'rinadi, maydon aria-invalid va aria-describedby", () => {
    const { maydon, yoz } = chiz({
      tur: "xato",
      xabar: "Vazifa matnini yozing.",
    });
    yoz("");
    fireEvent.submit(maydon.form!);
    expect(screen.getByRole("status").textContent).toBe(
      "Vazifa matnini yozing.",
    );
    expect(maydon.getAttribute("aria-invalid")).toBe("true");
    …
    // Tuzatildi — xato o'sha zahoti yo'qoladi (state)
    yoz("Non olish");
    expect(screen.getByRole("status").textContent).toBe("");
    expect(maydon.hasAttribute("aria-invalid")).toBe(false);
  });

npx vitest run --reporter=verbose (yangi fayl qatorlari):

text
 ✓ tekshiruv/YangiVazifaForma.test.tsx > YangiVazifaForma > boshlang'ich qiymat — qoralama 147ms
 ✓ tekshiruv/YangiVazifaForma.test.tsx > YangiVazifaForma > xato: matni ko'rinadi, maydon aria-invalid va aria-describedby 41ms
 ✓ tekshiruv/YangiVazifaForma.test.tsx > YangiVazifaForma > qo'shildi — maydon bo'shaydi (qoralamaga qaytmaydi) 9ms
 ✓ tekshiruv/YangiVazifaForma.test.tsx > YangiVazifaForma > saqlanmadi (oflayn) — matn maydonda qoladi 7ms
 ✓ tekshiruv/YangiVazifaForma.test.tsx > YangiVazifaForma > teg takliflari: # dan keyin chiqadi, tanlansa — qo'yiladi va yopiladi 25ms
 ✓ tekshiruv/YangiVazifaForma.test.tsx > YangiVazifaForma > Esc — takliflar yopiladi, matn joyida 13ms
 Test Files  2 passed (2)
      Tests  14 passed (14)

8 ta eski va 6 ta yangi. npm run tip va npm run lint — toza. Commit hali yo'q — branch State III darsida tugaydi.

10. Ko'p uchraydigan xatolar

Belgi Sabab Davosi
bosildi, ekran o'zgarmadi oddiy let useState
Too many re-renders renderda setter: onClick={setX(…)} onClick={() => setX(…)}
props o'zgardi, state eski useState(props.x) faqat birinchi marta props'ni to'g'ridan-to'g'ri ishlating
sahifa sekin, har chizishda og'ir hisob useState(hisobla()) useState(() => hisobla())
SetStateAction<null> xatosi useState(null) useState<string | null>(null)
useState is called conditionally hook if ichida yoki return dan keyin yuqori darajaga
setter'dan keyin eski qiymat snapshot State II

11. Mashqlar

Mashqlar kurs/mashqlar/17/12-usestate/ da. Har yechimni «Ishga tushir» bilan sinang.

1-mashq (oson): render jadvali

jsx
function Tea() {
  const [cups, setCups] = useState(2);
  return (
    <>
      <p>Choy: {cups}</p>
      <button onClick={() => setCups(cups * 2)}>×2</button>
      <button onClick={() => setCups(0)}>Tozalash</button>
    </>
  );
}

Mijoz "×2", "×2", "Tozalash", "×2" ni bosdi. Jadvalni to'ldiring:

Qadam State Ekranda
boshida cups: 2 Choy: 2
«×2» cups: 4 Choy: 4
«×2» cups: Choy: 8
«Tozalash» cups: 0 Choy: 0
«×2» cups: Choy: 0
Yechim

8 va 0. Oxirgi qator — 0 * 2 = 0. Setter har safar joriy state'dan hisoblaydi, boshlang'ich 2 ga qaytmaydi: useState(2) dagi 2 faqat birinchi chizishda ishlatildi.

2-mashq (o'rta): stol bron qilish

Booking komponentini yozing. State: guests (1 dan boshlanadi), tea (boolean, false). Talablar:

  • "−" va "+" tugmalari mehmonlar sonini o'zgartiradi, "−" 1 da disabled, "+" 8 da disabled;
  • "Ko'k choy" katagi (<input type="checkbox">, controlled: checked={tea} va onChange);
  • pastda: "4 kishi, osh: 140000 so'm" va choy tanlansa — "+ choy: 20000 so'm" (har kishiga 5000);
  • jami summa state'da emas — hisoblanadi.

Blok: "+" uch marta, katakni bosadi. Ekranda:

text
−+
Ko'k choy
4 kishi, osh: 140000 so'm
+ choy: 20000 so'm
Jami: 160000 so'm
Yechim
jsx
import { useState } from "react";

function Booking() {
  const [guests, setGuests] = useState(1);
  const [tea, setTea] = useState(false);
  const plov = guests * 35000;
  const teaPrice = tea ? guests * 5000 : 0;

  return (
    <>
      <div>
        <button
          disabled={guests === 1}
          onClick={() => setGuests(guests - 1)}
        >
          −
        </button>
        <button
          disabled={guests === 8}
          onClick={() => setGuests(guests + 1)}
        >
          +
        </button>
      </div>
      <label>
        <input
          type="checkbox"
          checked={tea}
          onChange={(event) => setTea(event.target.checked)}
        />
        Ko'k choy
      </label>
      <p>
        {guests} kishi, osh: {plov} so'm
      </p>
      {tea && <p>+ choy: {teaPrice} so'm</p>}
      <p>Jami: {plov + teaPrice} so'm</p>
    </>
  );
}

function App() {
  return <Booking />;
}

Ekranda:

text
−+
Ko'k choy
4 kishi, osh: 140000 so'm
+ choy: 20000 so'm
Jami: 160000 so'm

{tea && …} — tea boolean, 0 tuzog'i yo'q (Shartli render). plov, teaPrice va jami — state emas: ular guests va tea dan hisoblanadi va hech qachon "eskirib" qolmaydi.

3-mashq (qiyin): xato xabarli forma (TypeScript)

«Bahor» telefon raqami formasi, .tsx da. Uncontrolled <input name="phone"> va "Saqlash" tugmasi. State: error: string | null va saved: string | null.

  • Yuborishda (onSubmit, preventDefault) raqam FormData dan o'qiladi va probellar olib tashlanadi;
  • +998 bilan boshlanib, jami 13 belgi bo'lmasa — setError("Raqam +998 bilan va 12 raqamdan iborat bo'lsin"), maydonda aria-invalid;
  • to'g'ri bo'lsa — setError(null), setSaved(raqam), ekranda "Saqlandi: +998900000000".

Blok noto'g'ri raqam yozib yuboradi — to'g'risini o'zingiz sinaysiz. Ishora: onInput kerak emas — tekshiruv faqat yuborishda.

Yechim
tsx
import { useState } from "react";
import type { FormEvent } from "react";

export default function App() {
  const [error, setError] = useState<string | null>(null);
  const [saved, setSaved] = useState<string | null>(null);

  function handleSubmit(event: FormEvent<HTMLFormElement>) {
    event.preventDefault();
    const raw = new FormData(event.currentTarget).get("phone");
    const phone = String(raw ?? "").replaceAll(" ", "");
    if (!phone.startsWith("+998") || phone.length !== 13) {
      setError("Raqam +998 bilan va 12 raqamdan iborat bo'lsin");
      return;
    }
    setError(null);
    setSaved(phone);
  }

  return (
    <form onSubmit={handleSubmit}>
      <input
        name="phone"
        aria-label="Telefon"
        aria-invalid={error !== null ? true : undefined}
      />
      <button type="submit">Saqlash</button>
      <p role="status">{error}</p>
      {saved !== null && <p>Saqlandi: {saved}</p>}
    </form>
  );
}

Ekranda:

text
Saqlash
Raqam +998 bilan va 12 raqamdan iborat bo'lsin

Blok noto'g'ri raqamni yubordi — xato paragrafi to'ldi. Natija oynasida endi to'g'ri raqam yozing: +998 90 000 00 00. Yuborilganda setError(null) xatoni bo'shatadi va "Saqlandi: +998900000000" paydo bo'ladi. String(raw ?? "") — FormData.get string | File | null qaytaradi (FormData). TypeScript satr metodlarini faqat satrda ruxsat etadi.

4-mashq: Vazifalar qadami

refactor/forma-usestate branch'ida src/YangiVazifaForma.tsx, turlar.ts dagi QoshishNatijasi va tekshiruv/YangiVazifaForma.test.tsx ni kanondagidek yozing. npm test — Vitest 14/14, npm run tip toza bo'lsin. Savol: forma hali sahifaga ulanmagan — npm run knip buni topadimi?

Yechim

knip jim tugaydi — biz ham shuni kutmagandik. Sababi: knip Vitest sozlamasini o'qiydi va test fayllarini ham kirish nuqtasi deb hisoblaydi. YangiVazifaForma.test.tsx komponentni import qiladi — demak u "ishlatilgan". Saboq: knip toza bo'lishi — komponent sahifada ekanini anglatmaydi. Forma hali ilovada yo'q, buni faqat brauzerda ko'rasiz. State III darsida render.tsx dagi formaniChiz() uni sahifaga ulaydi.

12. Real ishda

  • useState — React'dagi eng ko'p ishlatiladigan hook: ochiq-yopiq menyu, tanlangan tab, forma maydonlari, yuklanish holati.
  • Kod ko'rigida ko'p uchraydigan izohlar: "bu state emas, hisoblang", "useState(props.x) — sinxron bo'lmaydi", "useState(null) ga tur bering".
  • State qayerda yashashi — katta ilovalarda asosiy arxitektura savoli. Komponent ichida, otada, Context'da (Context I) yoki tashqi kutubxonada (keyingi qismlarda).
  • Intervyu savollari: "Nega oddiy o'zgaruvchi ishlamaydi?", "setState dan keyin qiymat darhol o'zgaradimi?", "Lazy init nima?", "Ikki bir xil komponent state'ni bo'lishadimi?".

Xulosa

  • State — chizishlar orasida saqlanadigan xotira: const [x, setX] = useState(boshlang'ich).
  • Setter qiymatni React'ga beradi va qayta chizishni rejalashtiradi; komponent qayta chaqirilganda useState yangi qiymatni qaytaradi.
  • Oddiy let har chaqiruvda noldan boshlanadi va React'ga xabar bermaydi.
  • Har komponent nusxasining o'z state'i bor; state daraxtdagi o'ringa bog'langan va shaxsiy.
  • Boshlang'ich qiymat faqat birinchi chizishda; og'ir hisob — useState(() => …); TypeScript'da useState<string | null>(null).
  • vazifalar da forma xatosi va teg takliflari — state, atributlar undan chiziladi; Vitest 14/14.

Keyingi dars: State II: snapshot, batching va updater funksiya — nega setCount(count + 1) uch marta yozilsa ham son bittaga oshadi va buni qanday to'g'rilash.

Manbalar

  • React: "State: A Component's Memory" — react.dev/learn/state-a-components-memory
  • React: useState — "Avoiding recreating the initial state" — react.dev/reference/react/useState
  • React: "Rules of Hooks" — react.dev/reference/rules/rules-of-hooks
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
State I: useState — React komponentining xotirasi va qayta render — IlmHamroh