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

State'ni ko'tarish: yagona haqiqat manbai va callback props

Qisqacha: Ikki komponent bir ma'lumotni ko'rishi yoki o'zgartirishi kerak bo'lsa, state ularning eng yaqin umumiy otasiga ko'tariladi. Ota qiymatni props bilan pastga beradi, bolalar esa o'zgarishni callback props (onSelect, onAdd) orqali yuqoriga xabar qiladi: ma'lumot pastga oqadi, hodisa yuqoriga. Har ma'lumotning bitta egasi bo'ladi — yagona haqiqat manbai. O'z state'iga ega komponent — uncontrolled, hamma narsani props'dan oladigan komponent — controlled.

Bu darsda

  • Ikki qo'shni komponent bir state'ni ulashishi kerakligini taniysiz.
  • State'ni uch qadamda umumiy otaga ko'tarasiz.
  • Callback props yozasiz va nomlaysiz (onX / handleX).
  • Controlled va uncontrolled komponent farqini tushuntira olasiz.
  • State qayerda turishini aniqlash tartibini qo'llaysiz: eng yaqin umumiy ota, lekin keragidan baland emas.
  • vazifalar ning butun asosiy UI'si bitta App ostiga yig'iladi.

Oldin bilishingiz kerak: State strukturasini loyihalash, Props, Hodisalarni boshqarish.

1. Nega bu kerak?

Oldingi darsda bitta komponent ichidagi state'ni tartibga soldik: hosilaviy qiymat, id, status. Endi ikki komponent o'rtasidagi savol.

«Bahor» saytida "Ko'p so'raladigan savollar" bo'limi bor: "Ish vaqti", "Manzil", "Yetkazib berish". Har savol bosilsa, javobi ochiladi. Jasur akaning talabi: bir vaqtda faqat bitta javob ochiq tursin — telefon ekrani kichik. Sardor har savol uchun Panel komponenti yozdi, har birida o'z isOpen state'i. Natijada har panel o'zicha ochiladi va ikkinchisidan bexabar.

Muammo shundaki, "qaysi panel ochiq" degan ma'lumot birorta panelga tegishli emas. U hammasiga tegishli. Har panel o'z bo'lagini saqlasa, ularni kelishtiradigan joy yo'q.

Hayotiy o'xshatish: oshxonada bitta zal va uchta ofitsiant. Har ofitsiant "qaysi stol bo'sh" ni o'z daftariga yozsa, ikkitasi bir stolga mehmon o'tqazadi. To'g'ri yo'l — eshik oldidagi administrator: stollar ro'yxati unda. Ofitsiant "7-stolni bersam bo'ladimi?" deb so'raydi, administrator yozadi va hammaga aytadi.

React'da administrator — umumiy ota komponent. Bu usul state'ni ko'tarish (lifting state up) deyiladi.

2. Muammoni ko'ramiz

2.1 Har panelning o'z state'i

jsx
import { useState } from "react";

function Panel({ title, children }) {
  const [isOpen, setIsOpen] = useState(false);
  return (
    <section>
      <h3>
        <button onClick={() => setIsOpen(!isOpen)}>{title}</button>
      </h3>
      {isOpen && <p>{children}</p>}
    </section>
  );
}

function App() {
  return (
    <>
      <Panel title="Ish vaqti">Har kuni 07:00–23:00</Panel>
      <Panel title="Manzil">Toshkent, Chilonzor</Panel>
    </>
  );
}

Ekranda:

text
Ish vaqti
Har kuni 07:00–23:00
Manzil
Toshkent, Chilonzor

Ikkala javob ham ochiq. Har Panel nusxasining o'z xotirasi bor (useState darsida: "har nusxaning o'z state'i"). Birinchi panel ikkinchisining isOpen ini ko'rmaydi va o'zgartira olmaydi.

flowchart TD
  A["App — state yo'q"] --> P1["Panel: isOpen = true"]
  A --> P2["Panel: isOpen = true"]

Ikki panel bir-biridan bexabar, App esa umuman hech narsa bilmaydi.

3. Uch qadamda ko'tarish

React hujjatidagi tartib uch qadamdan iborat. Har qadamda kod ishlaydi — faqat xulq o'zgaradi.

3.1 1-qadam: bolada state'ni olib tashlash

isOpen ni Panel dan olib tashlaymiz. Uning o'rniga Panel qiymatni props'dan oladi:

jsx
function Panel({ title, children, isOpen }) {
  // const [isOpen, setIsOpen] = useState(false);  ← olib tashlandi

Endi Panel "ochiqmi?" degan savolga o'zi javob bermaydi — kim chizsa, o'sha aytadi.

3.2 2-qadam: otada qattiq qiymat bilan sinash

Ota hozircha qo'lda beradi: birinchisi ochiq, ikkinchisi yopiq.

jsx
function Panel({ title, children, isOpen }) {
  return (
    <section>
      <h3>
        <button>{title}</button>
      </h3>
      {isOpen && <p>{children}</p>}
    </section>
  );
}

function App() {
  return (
    <>
      <Panel title="Ish vaqti" isOpen={true}>
        Har kuni 07:00–23:00
      </Panel>
      <Panel title="Manzil" isOpen={false}>
        Toshkent, Chilonzor
      </Panel>
    </>
  );
}

Ekranda:

text
Ish vaqti
Har kuni 07:00–23:00
Manzil

isOpen={true} ni false ga almashtirib sinab ko'ring. Ekran faqat props'ga bog'liq — bu qadam to'g'ri bajarilganini tekshirish usuli. Tugmalar hozircha hech narsa qilmaydi.

3.3 3-qadam: state otada, setter — callback orqali

Endi App ga state qo'shamiz. U nimani eslab qolishi kerak? "Qaysi panel ochiq" — bitta qiymat: panel raqami yoki id. Oldingi darsdagi qoida: ikki boolean emas, bitta qiymat.

jsx
import { useState } from "react";

function Panel({ title, children, isOpen, onShow }) {
  return (
    <section>
      <h3>
        <button onClick={onShow}>{title}</button>
      </h3>
      {isOpen && <p>{children}</p>}
    </section>
  );
}

function App() {
  const [openId, setOpenId] = useState(null);
  return (
    <>
      <Panel
        title="Ish vaqti"
        isOpen={openId === "vaqt"}
        onShow={() => setOpenId("vaqt")}
      >
        Har kuni 07:00–23:00
      </Panel>
      <Panel
        title="Manzil"
        isOpen={openId === "manzil"}
        onShow={() => setOpenId("manzil")}
      >
        Toshkent, Chilonzor
      </Panel>
    </>
  );
}

Ekranda:

text
Ish vaqti
Manzil
Toshkent, Chilonzor

Endi "Manzil" ochilganda "Ish vaqti" o'zi yopildi. Qadamma-qadam:

Qadam openId (App'da) 1-panel isOpen 2-panel isOpen
boshida null false false
«Ish vaqti» "vaqt" true false
«Manzil» "manzil" false true

Bola tugma bosilganini sezadi, lekin state'ni o'zgartirmaydi — u faqat otaga xabar beradi: onShow(). Ota esa state'ni yangilaydi va ikkala panelni yangi props bilan qayta chizadi.

flowchart TD
  A["App: openId"] -- "isOpen (props)" --> P1["Panel"]
  A -- "isOpen (props)" --> P2["Panel"]
  P1 -. "onShow() — hodisa" .-> A
  P2 -. "onShow() — hodisa" .-> A

Uzluksiz chiziq — ma'lumot pastga, punktir — hodisa yuqoriga. Bu React'ning asosiy oqimi: ma'lumot bir tomonga (one-way data flow) oqadi. Bola otaning state'ini to'g'ridan-to'g'ri o'zgartira olmaydi — faqat ota bergan funksiyani chaqiradi.

Tekshirib ko'ring: Panel ichida onShow o'rniga onShow() yozilsa (onClick={onShow()}), nima bo'ladi?

Javob

onShow() render paytida chaqiriladi: App state'ni o'zgartiradi, bu yangi render chaqiradi, u yana onShow() ni chaqiradi — cheksiz sikl. React uni to'xtatadi: Too many re-renders. React limits the number of renders to prevent an infinite loop. Funksiyaning o'zini bering: onClick={onShow} (Hodisalarni boshqarish darsidagi qoida).

4. Callback props

4.1 Nomlash odati

Callback prop — ota bolaga beradigan funksiya; bola uni biror hodisa bo'lganda chaqiradi. Hodisa ishlovchisi kabi, nomi on bilan boshlanadi: onShow, onAdd, onSelect. Ota tomonda bu funksiyani yasaydigan ichki funksiya ko'pincha handle bilan nomlanadi:

jsx
function App() {
  function handleAdd(id) {   // ota: "ishlovchi"
    // …state'ni yangilash
  }
  return <MenuItem onAdd={handleAdd} />; // bola: "on…" — hodisa
}

Bu majburiy qoida emas, odat. Lekin begona kodni o'qiganda yordam beradi: on… ni ko'rsangiz — "bu prop hodisa", handle… ni ko'rsangiz — "bu shu komponentdagi ishlovchi".

4.2 Argument bilan xabar berish

Callback qiymat ham uzata oladi. «Bahor» menyusida taomni savatga qo'shish tugmasi bola komponentda, savat esa sarlavhada — boshqa komponentda. Ikkalasi bir ma'lumotni (savatni) ko'radi, shuning uchun state ularning umumiy otasi — App da:

jsx
import { useState } from "react";

const MENU = [
  { id: 1, name: "Osh", price: 35000 },
  { id: 2, name: "Choy", price: 5000 },
];

function Header({ count, total }) {
  return <h2>Savat: {count} ta, {total} so'm</h2>;
}

function MenuItem({ item, onAdd }) {
  return (
    <p>
      {item.name} — {item.price}{" "}
      <button onClick={() => onAdd(item.id)}>{item.name} +</button>
    </p>
  );
}

function App() {
  const [cartIds, setCartIds] = useState([]); // qo'shilgan id'lar
  const count = cartIds.length;
  const total = cartIds.reduce(
    (sum, id) => sum + MENU.find((m) => m.id === id).price,
    0,
  );

  function handleAdd(id) {
    setCartIds([...cartIds, id]);
  }

  return (
    <>
      <Header count={count} total={total} />
      {MENU.map((item) => (
        <MenuItem key={item.id} item={item} onAdd={handleAdd} />
      ))}
    </>
  );
}

Ekranda:

text
Savat: 3 ta, 75000 so'm
Osh — 35000 Osh +
Choy — 5000 Choy +

MenuItem savatdan bexabar — u faqat "menga onAdd berildi, bosilganda id'imni aytaman" deydi. Header ham bexabar — u faqat ikki son oladi. Savatning yagona egasi — App. count va total — hosilaviy qiymatlar.

onAdd={handleAdd} — funksiyaning o'zi uzatiladi. MenuItem ichida esa () => onAdd(item.id) — strelkali funksiya, chunki argument kerak.

4.3 Ma'lumot oqimi jadvali

Kim Nima biladi Nima qila oladi
App savat (cartIds) state'ni yangilash
Header count, total (props) faqat ko'rsatish
MenuItem item, onAdd (props) otaga "qo'shish bosildi" deb aytish

Bolalar ota haqida hech narsa bilmaydi va uning state nomini ham bilmaydi. Shuning uchun MenuItem ni boshqa joyda (masalan, "Tavsiyalar" bo'limida) qayta ishlatish oson: kim ishlatsa, onAdd ni o'zi beradi.

5. Controlled va uncontrolled komponent

Birinchi Panel (o'z isOpen state'i bilan) va oxirgi Panel (props'dan isOpen oladigan) — bir xil ko'rinadi, lekin turi boshqa:

Uncontrolled Controlled
Asosiy qiymat qayerda komponentning o'z state'ida otada, props bilan keladi
Ota ta'sir qila oladimi yo'q ha, to'liq
Ishlatish oson: <Panel title="…"> ko'proq yozuv: isOpen, onShow
Qachon tanlanadi qiymat boshqalarga kerak emas qiymatni boshqalar bilan kelishtirish kerak

Uncontrolled komponent — muhim ma'lumotini o'zi saqlaydi. Controlled komponent — muhim ma'lumoti props bilan boshqariladi, o'zgarishni callback bilan xabar qiladi. Bu so'zlar formadagi <input> dan kelgan. Formalar I darsida input'ning controlled shaklini, Formalar II da uncontrolled shaklini ko'ramiz — g'oya aynan shu.

Komponent ikkalasini birga qo'llashi ham mumkin: ota isOpen bersa — controlled, bermasa — o'zi eslab qoladi. Bunday komponent API'sini loyihalash — 18-qismda.

6. State qayerda turishi kerak?

6.1 Tartib

Har state uchun quyidagi savollar:

  1. Bu state'ga asoslanib nimani chizadigan komponentlarni toping.
  2. Ularning eng yaqin umumiy otasini toping (daraxtda ikkalasidan yuqoridagi birinchi komponent).
  3. State'ni o'sha otaga qo'ying. Mos ota bo'lmasa — faqat shu state'ni saqlash uchun yangi komponent yarating.

«Bahor» menyu sahifasini tasavvur qiling:

flowchart TD
  App --> Header["Header — savat soni"]
  App --> Page["MenuPage"]
  Page --> Filter["CategoryFilter"]
  Page --> List["MenuList"]
  List --> Item["MenuItem — qo'shish"]
  • Tanlangan kategoriya: uni CategoryFilter (qaysi tugma tanlangan) va MenuList (nimani ko'rsatish) ishlatadi. Umumiy ota — MenuPage. State — MenuPage da.
  • Savat: uni Header (son) va MenuItem (qo'shish) ishlatadi. Umumiy ota — App. State — App da.

6.2 Keragidan baland emas

Hamma state'ni App ga ko'tarib qo'yish ham xato. Masalan, "Ish vaqti" panelining ochiqligi faqat FAQ bo'limiga kerak. Uni App da saqlasangiz, uch muammo paydo bo'ladi:

  • App dagi har o'zgarish butun sahifani qayta chizadi (qayta render narxi — 18-qismda);
  • state'ni FAQ'gacha bir necha qavat props bilan "tashish" kerak bo'ladi — buni props drilling deyishadi, yechimi — Context;
  • App hamma narsani biladigan "xudo-komponent"ga aylanadi.

Qoida: state'ni uni ishlatadigan komponentlarga iloji boricha yaqin saqlang. Ko'tarish faqat ulashish kerak bo'lganda. Bunga colocation ("yonma-yon joylash") deyiladi — Kichik React loyihani tashkil qilish darsida fayllar uchun ham shu g'oyani ko'ramiz.

6.3 Yagona haqiqat manbai

Har noyob ma'lumot uchun bitta komponent bor — u state'ni saqlaydi va yangilaydi. Boshqalar faqat props'dan o'qiydi. Bu yagona haqiqat manbai (single source of truth). U oldingi darsdagi qoidaning davomi: bitta komponent ichida "takrorlamang", komponentlar orasida esa "bitta egasi bo'lsin".

Ikki joyda saqlangan bir ma'lumot har doim bir-biridan ajralib qolish xavfida — Sardorning jami narxi, eskirib qolgan tanlangan taom, ikki ochiq panel. Hammasi bitta kasallik.

Tekshirib ko'ring: Kategoriya filtri CategoryFilter va MenuList da alohida state bo'lsa, qanday xato chiqishi mumkin?

Javob

Filtr tugmasi "Ichimliklar" ni tanlangan ko'rsatadi, ro'yxat esa hali "Hammasi" ni chizadi — ikkala state'ni bir xil yangilash kerak, birini unutish kifoya. Bundan tashqari, CategoryFilter o'z state'ini MenuList ga yetkazishning to'g'ri yo'li yo'q: qo'shni komponentlar bir-biriga to'g'ridan-to'g'ri ma'lumot bera olmaydi. Yechim — state MenuPage da, ikkalasiga props.

7. Vazifalar qadami: bitta App ildizi

7.1 Oldin: bir nechta orol

Hozirgi vazifalar da (State III darsidagi holat) React bir nechta joyda alohida ishlaydi. Yangi vazifa formasi — o'z ildizi (createRoot(formaJoyi)), ro'yxat qatorlari — o'z ildizi (createRoot(royxat)). Qidiruv, filtr tugmalari, hisob, teglar, aloqa qatori, o'chirish dialogi esa hali index.html da va ularni render.tsx dagi vanilla funksiyalar yangilaydi. Bu orollar bir-biridan bexabar.

Natija — bugungi darsning muammosi kichik masshtabda. Masalan, formadagi teg takliflari (#ish yozilganda "ishxona" taklifi) teglar ro'yxatiga bog'liq. Teglar daraxti esa teglarniYoz() ichida, boshqa joyda quriladi. Forma ildizi bir marta chiziladi va teglar o'zgarganini o'zi bilmaydi.

7.2 Keyin: bitta ota

index.html dagi uzun blok bitta <div> ga aylanadi (refactor/app-ildizi branch'ida davom etamiz, oldingi darsda ochilgan):

diff
   <main>
     <h1>Vazifalar</h1>
-    <p id="aloqa" role="status"></p>
-    <button type="button" id="qayta-tugma"
-            hidden>Qayta urinish</button>
-    <!-- Yangi vazifa formasi — React (17/#12, YangiVazifaForma.tsx) -->
-    <div id="yangi-forma-joyi"></div>
-    <search>
…
-    <p id="hisob" aria-live="polite"></p>
-    <p id="teglar" hidden></p>
-    <p id="elon" role="status"></p>
+    <!-- React ildizi (17/#16): aloqa, forma, qidiruv, filtr, ro'yxat,
+         hisob, e'lon va o'chirish dialogi — src/App.tsx -->
+    <div id="ilova"></div>
     <h2>Eksport</h2>

(… — 25 ta olib tashlangan qator qisqartirildi: qidiruv, filtrlar, saralash, ro'yxat.) Eksport va import hali vanilla — ular Formalar II darsida ko'chadi.

Yangi src/App.tsx ning asosiy qismi (kanondan; importlar va boshMatnlar qisqartirildi):

tsx
/** App'dan chiqadigan hodisalar — ularni asosiy.ts bajaradi */
export type IlovaAmallari = {
  qator: QatorAmallari;
  xatoniTop: (matn: string) => string | null;
  onYoz: (matn: string) => void;
  onQosh: (matn: string) => QoshishNatijasi;
  onQidir: (matn: string) => void;
  onFiltr: (filtr: Filtr) => void;
  onSaralash: (qiymat: string) => void;
  onQaytaUrin: () => void;
};

type Props = {
  holat: Readonly<Holat>;
  qoralama: string; // forma ochilgandagi matn
  amallar: IlovaAmallari;
};

export function App({ holat, qoralama, amallar }: Props) {
  const { vazifalar, filtr, saralash, qidiruv } = holat;
  // … hosilaviy qiymatlar (17/#15): royxat, korinadigan, qoldi,
  //   bajarildi, teglar, daraxt
  return (
    <>
      <Aloqa tarmoq={holat.tarmoq} onQaytaUrin={amallar.onQaytaUrin} />
      <YangiVazifaForma
        boshlangichMatn={qoralama}
        xatoniTop={amallar.xatoniTop}
        takliflarniTop={(joriy) => tegTakliflari(daraxt, joriy)}
        onYoz={amallar.onYoz}
        onQosh={amallar.onQosh}
      />
      <QidiruvMaydoni
        qidiruv={qidiruv}
        topildi={korinadigan.length}
        onQidir={amallar.onQidir}
      />
      <Filtrlar filtr={filtr} onFiltr={amallar.onFiltr} />
      {/* … SaralashTanlovi, sarlavha, ro'yxat, bosh xabar */}
      <Statistika
        qoldi={qoldi}
        bajarildi={bajarildi}
        teglar={teglar}
        yopilgan={royxat.yopilganTeglar()}
      />
      <p id="elon" role="status">
        {holat.elon}
      </p>
      <OchirishDialog matn={holat.ochirilayotgan} />
    </>
  );
}

Bugungi darsni shu kodda toping:

  • Callback props: IlovaAmallari — App dan chiqadigan hodisalar ro'yxati, har biri on…. Filtrlar faqat onFiltr(f) ni chaqiradi — filtrni o'zi o'zgartirmaydi.
  • Yagona manba: takliflarniTop={(joriy) => tegTakliflari(daraxt, joriy)} — forma takliflari endi App hisoblagan o'sha daraxt dan. Teglar o'zgarsa, App qayta chiziladi va forma yangi funksiyani oladi. State III darsida knip topgan ortiqcha eksport — tegTakliflari — shu yerda teglar.ts ga ko'chdi va endi haqiqatan import qilinadi: knip toza.
  • Controlled komponentlar: QidiruvMaydoni va SaralashTanlovi maydonining qiymati props'dan (value={qidiruv}). Do'kon qidiruvni tozalasa (vazifa qo'shilganda), maydon ham bo'shaydi. Input'ning bu shakli — Formalar I darsida batafsil.

Filtrlar.tsx — eng qisqa controlled komponent (kanondan aynan):

tsx
export function Filtrlar({ filtr, onFiltr }: Props) {
  return (
    <div id="filtrlar" role="group" aria-label="Filtr">
      {FILTRLAR.map((f) => (
        <button
          key={f}
          type="button"
          data-filtr={f}
          aria-pressed={f === filtr}
          onClick={() => onFiltr(f)}
        >
          {NOMLAR[f]}
        </button>
      ))}
    </div>
  );
}

aria-pressed — filtr dan hisoblanadi. Avval vanilla filtrTugmalariniYoz() har tugmani aylanib setAttribute qilardi — endi bu kod yo'q.

7.3 State qayerda?

Diqqat qiling: App da birorta ham useState yo'q. Haqiqat manbai hozircha vanilla do'kon — holat.ts dagi holat obyekti (State III da u o'zgarmas massivlarga o'tgan). render.tsx endi ko'prik:

tsx
export function render(): void {
  if (ulangan === null) {
    return;
  }
  const { qoralama, amallar } = ulangan;
  // root.render() navbatga qo'yadi, DOM keyinroq yangilanadi. flushSync —
  // hozir, shu qatorda: keyingi kod (fokus, dialog) tayyor DOM'ga
  // qaraydi; birinchi chizish ham birinchi kadrga ulguradi (CLS 0).
  // {...holat} — nusxa: React'ga o'zgarmaydigan surat beramiz
  flushSync(() => {
    ildiz.render(
      <StrictMode>
        <App
          holat={{ ...holat }}
          qoralama={qoralama}
          amallar={amallar}
        />
      </StrictMode>,
    );
  });
}

Ya'ni App — butunlay controlled komponent: hamma narsa props'dan, hamma o'zgarish callback orqali asosiy.ts ga. Do'kon o'zgarsa, render() App ni yangi props bilan qayta chizadi. Bu strangler ("asta-sekin almashtirish") usuli: eski kod bo'lakma-bo'lak yangisi bilan almashadi va ilova biror qadamda ham ishlamay qolmaydi. Haqiqat manbai effektlar o'tilgach App ning o'z state'iga ko'chadi (useEffect III darsida).

asosiy.ts callback'larni beradi (parcha):

ts
ilovaniUla(qoralamaniOl(), {
  qator: {
    onAlmashtir(id) {
      elonQil("");
      holat.tahrirId = null;
      katakniAlmashtir(id, qoshniId(id));
    },
    // …
  },
  onQidir: qidiruvniYangila,
  // …
});

7.4 Haqiqiy tuzoq: Qidiruv.tsx va qidiruv.ts

Kanonni yozishda qidiruv komponenti birinchi bo'lib Qidiruv.tsx deb nomlangan edi. src/ da esa allaqachon qidiruv.ts (normallash funksiyalari) bor. Windows'da fayl nomi katta-kichik harfni farqlamaydi. ESLint shunday yiqildi:

text
Parsing error: …\src\Qidiruv.tsx was not found by the project service

"Tahlil xatosi: Qidiruv.tsx loyiha xizmati tomonidan topilmadi." TypeScript ikki nomni bitta fayl deb ko'rdi. Linux'da (CI, Vercel) esa ular ikki xil fayl — xato boshqa joyda, boshqacha ko'rinishda chiqishi mumkin edi. Tuzatish — farqli nom: QidiruvMaydoni.tsx, SaralashTanlovi.tsx. Qoida: komponent fayli nomi bir papkadagi boshqa fayldan faqat harf katta-kichikligi bilan farq qilmasin.

7.5 Tekshiruv va commit

bash
npm run build && npm test && npm run lint && npm run tip

Kanon natijalari: node:test — 149/149, Vitest — 18 (4 ta yangi App testi: "hisob va teglar ro'yxatdan hisoblanadi", "filtr: tanlangani aria-pressed, bosilsa — onFiltr" va boshqalar), brauzer tekshiruvi — 159/159. Diff: 13 fayl, +583 −349.

Commit:

text
refactor: bitta App ildizi — hosilaviy qiymatlar renderda

Body: "forma, qidiruv, filtr, saralash, ro'yxat, hisob, teglar, aloqa, e'lon, dialog — App.tsx ostida; ma'lumot — do'kondan props bilan. render.tsx — ko'prik". PR'ni birlashtiring.

8. Ko'p uchraydigan xatolar

Belgi Sabab Davosi
qo'shni komponentlar bir-biriga mos kelmaydi har birida o'z state nusxasi state'ni umumiy otaga ko'taring
bola otaning state'ini o'zgartira olmaydi setter bolaga berilmagan onX callback prop bering
Too many re-renders onClick={onShow()} onClick={onShow} yoki () => onShow(id)
App juda katta, har harf butun sahifani chizadi hamma state ildizda state'ni ishlatuvchilarga yaqin saqlang
onAdd is not a function ota prop'ni bermagan yoki nomi boshqa prop nomini tekshiring, TS'da tur yozing

Oxirgi xato jonli:

jsx
function MenuItem({ name, onAdd }) {
  return <button onClick={() => onAdd(name)}>{name} qo'shish</button>;
}

function App() {
  return <MenuItem name="Osh" onAd={() => {}} />;
}

Konsolda:

text
Uncaught TypeError: onAdd is not a function
TypeError: onAdd is not a function

Natija oynasining konsoli xatoni ikki qatorda ko'rsatadi, matn bir xil. Ota onAd (harf tushib qolgan) berdi, bola esa onAdd ni kutdi — undefined ni chaqirib bo'lmaydi. Xato bosilganda chiqadi, render paytida emas. TypeScript'da props turi yozilgan bo'lsa, bu xato kompilyatsiyada ushlanadi.

9. Mashqlar

Mashqlar kurs/mashqlar/17/16-state-kotarish/ da.

1-mashq (oson): Umumiy ota

Daraxt: App → (Header, MenuPage); MenuPage → (CategoryFilter, MenuList); MenuList → MenuItem. Header da "Sevimlilar: 2" yozuvi bor, MenuItem da esa "" tugmasi.

  • Sevimlilar ro'yxati qaysi komponentda turishi kerak:
  • MenuItem sevimliga qo'shishni otaga qaysi prop orqali bildiradi (nom odatiga ko'ra, masalan):
  • Faqat MenuList ishlatadigan "saralash" tanlovi qayerda tursa yetadi:
Yechim

Sevimlilarni Header va MenuItem ishlatadi — eng yaqin umumiy ota App. Callback nomi on bilan boshlanadi: onToggleFavorite (yoki onFavorite — muhimi on…). Saralash faqat MenuList ga kerak — uni ko'tarish shart emas (colocation).

2-mashq (o'rta): Stol xaritasi va bron qatori

Ikki qo'shni komponent: TableMap (stollar tugmalari: 1, 2, 3) va BookingSummary ("Tanlangan stol: 2" yoki "Stol tanlanmagan"). Hozir TableMap tanlovni o'z state'ida saqlaydi va BookingSummary uni ko'rmaydi. State'ni App ga ko'taring. TableMap — controlled: selected va onSelect props'ini oladi; tanlangan tugma aria-pressed bilan belgilansin.

Yechim
jsx
import { useState } from "react";

function TableMap({ selected, onSelect }) {
  return (
    <div>
      {[1, 2, 3].map((n) => (
        <button
          key={n}
          aria-pressed={n === selected}
          onClick={() => onSelect(n)}
        >
          {n}-stol
        </button>
      ))}
    </div>
  );
}

function BookingSummary({ table }) {
  return (
    <p>
      {table === null
        ? "Stol tanlanmagan"
        : `Tanlangan stol: ${table}`}
    </p>
  );
}

function App() {
  const [table, setTable] = useState(null);
  return (
    <>
      <TableMap selected={table} onSelect={setTable} />
      <BookingSummary table={table} />
    </>
  );
}

Ekranda:

text
1-stol2-stol3-stol
Tanlangan stol: 2

onSelect={setTable} — setter'ning o'zi callback bo'la oladi: u argument oladi va state'ni yangilaydi. TableMap buni bilmaydi — u faqat "bosilganda onSelect(n)" deydi.

3-mashq (qiyin): Savat sarlavhada, o'chirish ro'yxatda

TypeScript'da uch komponent: Header ("Savat: N ta, S so'm"), Menu (har taomga "qo'shish") va Cart (savatdagi har qator va "olib tashlash" tugmasi). Savat — App da, faqat id'lar massivi (bir taom bir necha marta bo'lishi mumkin). Talablar:

  • props turlari: onAdd: (id: number) => void, onRemove: (index: number) => void;
  • count va total — hosilaviy;
  • "O'chir" tugmasi massivdagi o'sha o'rindagi elementni olib tashlaydi (filter ikkinchi argumenti — indeks; filter).

Menyu: osh 35 000, lag'mon 28 000. Sinov: osh, lag'mon, osh qo'shing va savatdagi birinchi qatorni olib tashlang.

Yechim
tsx
import { useState } from "react";

type Dish = { id: number; name: string; price: number };

const MENU: Dish[] = [
  { id: 1, name: "Osh", price: 35000 },
  { id: 2, name: "Lag'mon", price: 28000 },
];

function findDish(id: number): Dish {
  const dish = MENU.find((d) => d.id === id);
  if (dish === undefined) throw new Error(`Taom yo'q: ${id}`);
  return dish;
}

function Header({ count, total }: { count: number; total: number }) {
  return <h2>Savat: {count} ta, {total} so'm</h2>;
}

function Menu({ onAdd }: { onAdd: (id: number) => void }) {
  return (
    <p>
      {MENU.map((d) => (
        <button key={d.id} onClick={() => onAdd(d.id)}>
          {d.name} +
        </button>
      ))}
    </p>
  );
}

type CartProps = {
  ids: readonly number[];
  onRemove: (index: number) => void;
};

function Cart({ ids, onRemove }: CartProps) {
  return (
    <ol>
      {ids.map((id, i) => (
        <li key={i}>
          {findDish(id).name}{" "}
          <button onClick={() => onRemove(i)}>O'chir {i + 1}</button>
        </li>
      ))}
    </ol>
  );
}

export default function App() {
  const [ids, setIds] = useState<number[]>([]);
  const total = ids.reduce((sum, id) => sum + findDish(id).price, 0);

  return (
    <>
      <Header count={ids.length} total={total} />
      <Menu onAdd={(id) => setIds([...ids, id])} />
      <Cart
        ids={ids}
        onRemove={(index) =>
          setIds(ids.filter((_, i) => i !== index))
        }
      />
    </>
  );
}

Ekranda:

text
Savat: 2 ta, 63000 so'm
Osh +Lag'mon +
Lag'mon O'chir 1
Osh O'chir 2

key={i} — bu yerda indeks kalit ataylab: qatorlarda o'z state'i yo'q va bir xil taom ikki marta bo'lishi mumkin, id noyob emas. Qatorlarda state bo'lsa, indeks kalit xavfli — buni keyingi darsda ko'ramiz. findDish topilmagan id'da xato tashlaydi — undefined.price dan ko'ra aniq xabar.

4-mashq: Vazifalar qadami

refactor/app-ildizi branch'ida «Vazifalar qadami: bitta App ildizi» bo'limidagi o'zgarishlarni kiriting: App.tsx, Aloqa.tsx, Filtrlar.tsx, QidiruvMaydoni.tsx, SaralashTanlovi.tsx, Statistika.tsx (oldingi darsdan), OchirishDialog.tsx, teglar.ts, render.tsx ko'prigi va index.html. Keyin tekshiruvlar va commit.

Yechim

Tekshiruv ro'yxati:

  • npm run lint — was not found by the project service chiqsa, src/ da faqat harf katta-kichikligi bilan farqlanadigan fayl nomlarini qidiring.
  • npm run knip — render.tsx dan olib tashlangan funksiyalar (hisobniYoz, matnniYoz) boshqa joyda qolmaganini ko'rsatadi.
  • npm test — node 149, Vitest 18.
  • Brauzerda: filtr tugmasini bosing — aria-pressed DevTools Elements'da almashadi; qidiruv maydoniga yozing — "N ta topildi" yangilanadi.

Yakuniy kod — refactor: bitta App ildizi — hosilaviy qiymatlar renderda commit'i. PR'ni birlashtiring.

10. Real ishda

  • Har kungi ish: "bu ma'lumot kimga tegishli?" — React'da eng ko'p beriladigan arxitektura savoli. Kod ko'rigida "bu state ikki joyda — ko'taring" yoki aksincha "bu state App'da nima qilyapti? — pastga tushiring" izohlari tez-tez uchraydi.
  • UI kutubxonalar (Radix, MUI, shadcn/ui) komponentlarida value/onValueChange va defaultValue juftligi — controlled va uncontrolled rejim bir komponentda. Nomlari shu darsdagi odatga mos.
  • Katta ilovalarda ko'tarish bir necha qavatga yetganda Context (Context I) yoki state kutubxonasi (Zustand, Redux — 19-qism) ishlatiladi. Lekin ular ham "yagona haqiqat manbai" g'oyasining boshqa ko'rinishi.
  • Intervyu savollari: "Lifting state up nima?", "Qo'shni komponentlar qanday ma'lumot almashadi?", "Controlled va uncontrolled komponent farqi?", "One-way data flow nima?".

Xulosa

  • Ikki komponent bir ma'lumotga muhtoj bo'lsa — state eng yaqin umumiy otaga ko'tariladi.
  • Ota qiymatni props bilan beradi, bola o'zgarishni on… callback bilan so'raydi: ma'lumot pastga, hodisa yuqoriga.
  • Controlled komponent — qiymat props'dan; uncontrolled — o'z state'idan.
  • State'ni keragidan baland ko'tarmang: uni ishlatuvchilarga yaqin saqlang.
  • Har ma'lumotning bitta egasi — yagona haqiqat manbai; ikki nusxa — xato manbai.
  • vazifalar ning asosiy UI'si bitta App ostida; App — controlled, haqiqat hozircha vanilla do'konda.

Keyingi dars: State'ning saqlanishi va key bilan reset — state komponentga emas, daraxtdagi o'rniga bog'langan: qachon u saqlanadi, qachon yo'qoladi va key bilan uni qanday ataylab noldan boshlash mumkin.

Manbalar

  • React: "Sharing State Between Components" — react.dev/learn/sharing-state-between-components
  • React: "Thinking in React" (4-qadam: state qayerda yashaydi) — react.dev/learn/thinking-in-react
  • React: "Responding to Events" (hodisa props nomlari) — react.dev/learn/responding-to-events
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
State'ni ko'tarish: yagona haqiqat manbai va callback props — IlmHamroh