Mundarija (32)
- Bu darsda
- 1. Nega bu kerak?
- 2. Beshta qoida
- 2.1 Bog'liq state'ni guruhlash
- 3. Ortiqcha state: hisoblab bo'ladigan narsani saqlamang
- 3.1 Sardorning xatosi — jonli
- 3.2 Tuzatish: renderda hisoblash
- 3.3 "Har renderda hisoblash sekin emasmi?"
- 3.4 Props'ni state'ga ko'chirish tuzog'i
- 4. Takrorlash: obyekt emas, id saqlang
- 4.1 Tanlangan taom eskirib qoldi
- 4.2 Tuzatish: id va hisoblangan obyekt
- 5. Qarama-qarshi flaglar o'rniga status
- 5.1 Ham yuborilmoqda, ham yuborildi
- 5.2 Bitta status — faqat ma'noli holatlar
- 5.3 TypeScript imkonsiz qiymatni to'xtatadi
- 6. Chuqur ichma-ich tuzilmani tekislash
- 6.1 Ichma-ich menyu
- 6.2 Tekis tuzilma
- 7. Vazifalar qadami: hisob va teglar — state emas
- 7.1 Oldin: DOM — xotira
- 7.2 Keyin: renderda hisoblanadi
- 7.3 Kanondagi boshqa qarorlar — shu dars ko'zi bilan
- 8. Ko'p uchraydigan xatolar
- 9. Mashqlar
- 1-mashq (oson): Ortiqchasini toping
- 2-mashq (o'rta): Navbatdagi stol
- 3-mashq (qiyin): Bron formasining statusi
- 4-mashq: Vazifalar qadami
- 10. Real ishda
- Xulosa
- Manbalar
State strukturasini loyihalash: hosilaviy qiymat, id va status
Qisqacha: Yaxshi state — kam state. Boshqa qiymatdan hisoblab olinadigan narsani (jami narx, hisob, filtrlangan ro'yxat) state'da saqlamang — renderda hisoblang. Bir ma'lumotni ikki joyda saqlamang: tanlangan taomning obyektini emas, id sini saqlang. Bir-biriga zid bo'lishi mumkin bo'lgan ikki boolean (
isSending,isSent) o'rniga bitta status qiymat ishlating. Ichma-ich chuqur tuzilmani tekis qiling. Har ortiqcha state — "biri yangilandi, ikkinchisi eskicha qoldi" degan xatoga ochiq eshik.
Bu darsda
- Qaysi qiymat state bo'lishi kerakligini, qaysi biri hisoblanishini ajrata olasiz.
- "Props'ni state'ga ko'chirish" tuzog'ini taniysiz va
initial…nomlash odatini bilasiz. - Tanlangan elementni obyekt bilan emas, id bilan saqlaysiz.
- Ikki boolean o'rniga bitta status yozasiz va TypeScript bilan imkonsiz qiymatni to'sasiz.
- Ichma-ich ma'lumotni tekis tuzilmaga aylantirasiz.
vazifalardagi hisob va teglar nega state emasligini ko'rasiz.
Oldin bilishingiz kerak: State III: obyekt va massivlarni immutable yangilash, State I: useState, Holatdan UI chizish: state → render.
1. Nega bu kerak?
Oldingi darsda state'ni to'g'ri yangilashni o'rgandik: obyekt va massivni o'zgartirmasdan, yangisini yasash. Bugungi savol boshqa: state'da nima turishi kerak?
Sardor «Bahor» kassasi uchun kichik panel yozdi. Panelda ikki state bor edi: savat (qaysi taomdan nechta) va jami narx. Taom qo'shilganda ikkalasi ham yangilanardi. Bir kuni u "−" tugmasini qo'shdi va jami narxni yangilashni unutdi. Kechqurun kassada pul hisobi chiqmadi: ekranda bitta osh turibdi, jami esa 70 000 so'm.
Xato bitta qatorda edi, lekin uning ildizi chuqurroq. Jami narx alohida state bo'lgani uchun uni har o'zgarishda qo'lda yangilash kerak edi. Bir joyda unutildi — ma'lumot ikkiga bo'lindi. Agar jami har renderda savatdan hisoblanganida, bu xatoni yozishning iloji ham bo'lmasdi.
Hayotiy o'xshatish: oshxonadagi ombor daftari. Har kirgan va chiqqan qopni bitta daftarga yozasiz. Agar "jami nechta qop" ni alohida daftarga ham yozsangiz, bir kun ikkinchisiga yozish esdan chiqadi va daftarlar bir-biriga zid bo'ladi. To'g'ri yo'l — bitta daftar, jami esa kerak bo'lganda sanaladi.
state → render darsida vanilla JS'da ham shu qoidani o'rgangan edik: "hosila ma'lumotni holatda saqlamang". React'da u yanada muhim. Har state o'zgarishi qayta render chaqiradi, har setter — yana bitta "unutish mumkin bo'lgan" qator.
2. Beshta qoida
React hujjati (react.dev, "Choosing the State Structure") state tuzilmasi uchun beshta qoidani beradi. Bugun hammasini misolda ko'ramiz:
| Qoida | Qisqasi | Nimadan saqlaydi |
|---|---|---|
| Bog'liqlarni guruhlang | doim birga o'zgaradigan ikki qiymat — bitta obyekt | birini unutish |
| Ziddiyatdan qoching | ikki boolean o'rniga bitta status | "ham yuborilmoqda, ham yuborildi" |
| Ortiqchasini olib tashlang | hisoblab bo'ladigan narsa state emas | jami eskirib qolishi |
| Takrorlamang | obyekt emas, id | ikki nusxadan biri eskirishi |
| Chuqur ichma-ich qilmang | tekis tuzilma | murakkab yangilash kodi |
Hammasining ortidagi g'oya bitta: har ma'lumotning bitta egasi bo'lsin. Qolgan hamma narsa undan hisoblanadi.
2.1 Bog'liq state'ni guruhlash
Eng yengil qoidadan boshlaymiz. Stol bron qilish formasida stol raqami va mehmonlar soni doim birga tanlansa, ularni bitta obyektga qo'yish qulay:
import { useState } from "react";
function App() {
const [table, setTable] = useState({ number: 4, guests: 2 });
return (
<div>
<p>
Stol {table.number}, {table.guests} kishi
</p>
<button onClick={() => setTable({ number: 7, guests: 6 })}>
Katta stol
</button>
</div>
);
}Ekranda:
Stol 4, 2 kishi
Katta stolIkkita alohida useState ham xato emas. Lekin ular har doim birga o'zgarsa, bitta setter — bitta "unutish imkoniyati" kam. Obyektni yangilashda esa oldingi darsdagi spread qoidasini eslang: setTable({ ...table, guests: 3 }) — number yo'qolmaydi.
Teskari holat ham bor: qiymatlar alohida o'zgarsa (qidiruv matni va tanlangan filtr), ularni bitta obyektga tiqish foyda bermaydi. Har safar spread yozishga to'g'ri keladi.
3. Ortiqcha state: hisoblab bo'ladigan narsani saqlamang
3.1 Sardorning xatosi — jonli
Sardorning panelini aynan takrorlaymiz. «Ishga tushir» ni bosing va tugmalarni sinang. Pastdagi natija — avval «Osh +», keyin «Choy +», keyin «Osh −» bosilgandan keyingi holat:
import { useState } from "react";
const PRICES = { osh: 35000, choy: 5000 };
function App() {
const [cart, setCart] = useState({ osh: 0, choy: 0 });
const [total, setTotal] = useState(0); // ❌ ortiqcha state
function add(dish) {
setCart({ ...cart, [dish]: cart[dish] + 1 });
setTotal(total + PRICES[dish]);
}
function remove(dish) {
if (cart[dish] === 0) return;
setCart({ ...cart, [dish]: cart[dish] - 1 });
// jami narxni kamaytirish esdan chiqdi
}
return (
<div>
<p>Osh: {cart.osh}, choy: {cart.choy}</p>
<p>Jami: {total} so'm</p>
<button onClick={() => add("osh")}>Osh +</button>{" "}
<button onClick={() => remove("osh")}>Osh −</button>{" "}
<button onClick={() => add("choy")}>Choy +</button>
</div>
);
}Ekranda:
Osh: 0, choy: 1
Jami: 40000 so'm
Osh + Osh − Choy +Savatda bitta ko'k choy, jami esa 40 000 so'm. Qadamma-qadam:
| Qadam | cart |
total |
Ekranda |
|---|---|---|---|
| boshida | osh 0, choy 0 | 0 | Jami: 0 |
| «Osh +» | osh 1, choy 0 | 35000 | Jami: 35000 |
| «Choy +» | osh 1, choy 1 | 40000 | Jami: 40000 |
| «Osh −» | osh 0, choy 1 | 40000 | Jami: 40000 |
Oxirgi qatorda ikki state bir-biriga zid. React aybdor emas: u ikkalasini ham sodiq chizdi. Xato — tuzilmada: total cart dan hisoblanadigan qiymat, lekin alohida saqlangan.
3.2 Tuzatish: renderda hisoblash
total ni state'dan olib tashlaymiz. U har renderda cart dan hisoblanadi:
import { useState } from "react";
const PRICES = { osh: 35000, choy: 5000 };
function App() {
const [cart, setCart] = useState({ osh: 0, choy: 0 });
// ✅ hosilaviy qiymat: har renderda savatdan hisoblanadi
const total = cart.osh * PRICES.osh + cart.choy * PRICES.choy;
function change(dish, step) {
const next = cart[dish] + step;
if (next < 0) return;
setCart({ ...cart, [dish]: next });
}
return (
<div>
<p>Osh: {cart.osh}, choy: {cart.choy}</p>
<p>Jami: {total} so'm</p>
<button onClick={() => change("osh", 1)}>Osh +</button>{" "}
<button onClick={() => change("osh", -1)}>Osh −</button>{" "}
<button onClick={() => change("choy", 1)}>Choy +</button>
</div>
);
}Ekranda:
Osh: 0, choy: 1
Jami: 5000 so'm
Osh + Osh − Choy +Endi yangilash funksiyasi bitta setter chaqiradi. total ni unutishning iloji yo'q — u setter emas, oddiy const. Har setCart dan keyin React App ni qayta chaqiradi (useState darsida ko'rganimiz kabi), total ham yangidan hisoblanadi.
Hosilaviy qiymat (derived state) — boshqa state yoki props'dan hisoblab olinadigan qiymat. Uni state'da saqlash shart emas. Qoidani savol shaklida eslab qoling: "Bu qiymatni mavjud state va props'dan hisoblay olamanmi?" Javob "ha" bo'lsa — u state emas.
Hosilaviy qiymatlarga misollar:
- jami narx, taomlar soni, "3 ta vazifa qoldi";
- filtrlangan yoki saralangan ro'yxat (
items.filter(…)); - "forma to'g'ri to'ldirilganmi" (
name.trim() !== ""); - ism va familiyadan to'liq ism.
3.3 "Har renderda hisoblash sekin emasmi?"
Ko'pincha yo'q. Bir necha mingta elementdan filter yoki yig'indi — millisekund ulushlari. Biz vazifalar da 1 000 vazifa bilan o'lchaganmiz: qidiruvda har harfda butun ilova qayta chizilishi va hisob, teglar, ko'rinadigan qatorlar yangidan hisoblanishi bilan birga — taxminan 16–19 ms (Chrome 154, oddiy noutbuk). Bu ko'z ilg'amaydigan vaqt. To'liq o'lchov — mini-ilova darsida.
Hisoblash haqiqatan og'ir bo'lsa (o'lchab ko'rilgan bo'lsa!), natijani eslab qolish usuli bor — useMemo. Uni memo, useMemo, useCallback darsida o'rganamiz. Muhimi: useMemo ham state emas, u ham hisoblaydi — faqat keshlaydi.
Tekshirib ko'ring: Komponentda
const [items, setItems](taomlar ro'yxati) vaconst [count, setCount](taomlar soni) bor. Qaysi biri ortiqcha va u nima bilan almashtiriladi?
Javob
count ortiqcha: u items.length ga teng. setCount ni olib tashlab, renderda const count = items.length; yozamiz. Aks holda taom o'chirilganda setCount ni unutish — Sardorning xatosi.
3.4 Props'ni state'ga ko'chirish tuzog'i
Ortiqcha state'ning yashirin turi bor: props'ni state'ga nusxalash. Narx yorlig'i komponenti narxni props'dan olib, state'ga yozadi:
import { useState } from "react";
function PriceTag({ price }) {
const [shown] = useState(price); // ❌ props nusxasi
return <p>Yorliqda: {shown} so'm</p>;
}
function App() {
const [price, setPrice] = useState(35000);
return (
<div>
<PriceTag price={price} />
<p>Menyuda: {price} so'm</p>
<button onClick={() => setPrice(price + 1000)}>
Narxni oshirish
</button>
</div>
);
}Ekranda:
Yorliqda: 35000 so'm
Menyuda: 36000 so'm
Narxni oshirishMenyuda narx o'zgardi, yorliqda — yo'q. Sabab: useState(price) dagi qiymat faqat birinchi renderda olinadi. Keyingi renderlarda React argumentga qaramaydi — state o'zining xotirasida. Ota yangi price yuborsa ham, shown o'zgarmaydi.
Tuzatish — state'ni olib tashlash. price ni to'g'ridan-to'g'ri ishlating:
function PriceTag({ price }) {
return <p>Yorliqda: {price} so'm</p>; // ✅ har doim yangi
}Ba'zan nusxa ataylab kerak bo'ladi: forma boshida props'dagi qiymat bilan to'ladi, keyin foydalanuvchi uni o'zi o'zgartiradi. Bunday props'ni initial… yoki default… deb nomlash odat bo'lgan: initialPrice, defaultName. Nom "keyingi o'zgarishlarga qaramayman" deb ochiq aytadi. vazifalar dagi tahrir maydoni Formalar I darsida aynan shunday bo'ladi: useState(vazifa.matn) — tahrir boshlanganda matn olinadi, keyin foydalanuvchi o'zi yozadi. Props o'zgarganda state'ni noldan boshlash kerak bo'lsa — key usuli bor, u State'ning saqlanishi darsida.
4. Takrorlash: obyekt emas, id saqlang
4.1 Tanlangan taom eskirib qoldi
Menyuda taomni tanlash mumkin, tanlangani pastda ko'rinadi. Har qatorda narxni 1 000 so'mga oshiradigan tugma ham bor. Sardor tanlangan taomni butun obyekti bilan saqladi:
import { useState } from "react";
const START = [
{ id: 1, name: "Osh", price: 35000 },
{ id: 2, name: "Manti", price: 30000 },
];
function App() {
const [items, setItems] = useState(START);
const [selected, setSelected] = useState(null); // ❌ nusxa
function raise(id) {
setItems(items.map((it) =>
it.id === id ? { ...it, price: it.price + 1000 } : it
));
}
return (
<div>
<ul>
{items.map((it) => (
<li key={it.id}>
{it.name} — {it.price}{" "}
<button onClick={() => setSelected(it)}>
{it.name} tanlash
</button>{" "}
<button onClick={() => raise(it.id)}>
{it.name} +1000
</button>
</li>
))}
</ul>
<p>Tanlangan: {selected ? selected.price : "yo'q"}</p>
</div>
);
}Ekranda:
Osh — 36000 Osh tanlash Osh +1000
Manti — 30000 Manti tanlash Manti +1000
Tanlangan: 35000Ro'yxatda osh 36 000, pastda — 35 000. Endi bir xil oshning ikki nusxasi bor: biri items ichida, ikkinchisi selected da. raise yangi obyekt yasadi (immutable yangilash to'g'ri ishladi), lekin selected hali eski obyektga qarab turibdi.
| Qadam | items[0].price |
selected |
Ekranda pastda |
|---|---|---|---|
| boshida | 35000 | null |
Tanlangan: yo'q |
| «Osh tanlash» | 35000 | osh obyekti (35000) | Tanlangan: 35000 |
| «Osh +1000» | 36000 (yangi obyekt) | eski obyekt (35000) | Tanlangan: 35000 |
4.2 Tuzatish: id va hisoblangan obyekt
Tanlangan taomning o'zini emas, faqat id sini saqlaymiz. Obyektning o'zi esa har renderda ro'yxatdan topiladi:
import { useState } from "react";
const START = [
{ id: 1, name: "Osh", price: 35000 },
{ id: 2, name: "Manti", price: 30000 },
];
function App() {
const [items, setItems] = useState(START);
const [selectedId, setSelectedId] = useState(null); // ✅ faqat id
const selected = items.find((it) => it.id === selectedId);
function raise(id) {
setItems(items.map((it) =>
it.id === id ? { ...it, price: it.price + 1000 } : it
));
}
return (
<div>
<ul>
{items.map((it) => (
<li key={it.id}>
{it.name} — {it.price}{" "}
<button onClick={() => setSelectedId(it.id)}>
{it.name} tanlash
</button>{" "}
<button onClick={() => raise(it.id)}>
{it.name} +1000
</button>
</li>
))}
</ul>
<p>Tanlangan: {selected ? selected.price : "yo'q"}</p>
</div>
);
}Ekranda:
Osh — 36000 Osh tanlash Osh +1000
Manti — 30000 Manti tanlash Manti +1000
Tanlangan: 36000selected endi hosilaviy qiymat: selectedId va items dan hisoblanadi. Narx qayerda o'zgarsa ham, pastdagi qator ergashadi.
Id saqlashning yana bir foydasi bor. Tanlangan taom ro'yxatdan o'chirilsa, items.find undefined qaytaradi. Ekranda "yo'q" chiqadi — o'chgan taom "arvoh" bo'lib qolmaydi. Obyekt saqlaganda esa o'chirilgan osh pastda tanlangan holda turaverardi.
Maslahat: Bir nechta tanlov (masalan, belgilangan taomlar) uchun ham shu qoida: obyektlar massivi emas, id'lar to'plami —
new Set([1, 3])yoki[1, 3]. Tekshiruv:selectedIds.has(it.id).
5. Qarama-qarshi flaglar o'rniga status
5.1 Ham yuborilmoqda, ham yuborildi
Buyurtma tugmasi bosilganda "Yuborilmoqda…" yozuvi chiqadi, yarim soniyadan keyin "Qabul qilindi!" ko'rinadi. Sardor ikkita boolean yozdi:
import { useState } from "react";
function App() {
const [isSending, setIsSending] = useState(false);
const [isSent, setIsSent] = useState(false);
function send() {
setIsSending(true);
setTimeout(() => {
setIsSent(true);
// setIsSending(false) esdan chiqdi
}, 500);
}
return (
<div>
<button onClick={send}>Buyurtma berish</button>
{isSending && <p>Yuborilmoqda…</p>}
{isSent && <p>Qabul qilindi!</p>}
</div>
);
}Ekranda:
Buyurtma berish
Yuborilmoqda…
Qabul qilindi!Ikki yozuv birga chiqdi. Ikki boolean — to'rtta mumkin bo'lgan holat: false/false, true/false, false/true va true/true. Oxirgisi — "ham yuborilmoqda, ham yuborildi" — ma'nosiz. Lekin kod uni yaratishga yo'l qo'yadi va bir qatorni unutish yetarli. Shu sabab yuborilayotganda tugma ham bloklanmagan — ikkinchi bosish ikkinchi buyurtmani yuborardi.
5.2 Bitta status — faqat ma'noli holatlar
Haqiqatda buyurtmaning uchta holati bor: tahrirda, yuborilmoqda, yuborildi. Ularni bitta qiymat bilan ifodalaymiz:
import { useState } from "react";
function App() {
// "tahrir" | "yuborilmoqda" | "yuborildi"
const [status, setStatus] = useState("tahrir");
function send() {
setStatus("yuborilmoqda");
setTimeout(() => setStatus("yuborildi"), 500);
}
if (status === "yuborildi") {
return <p>Qabul qilindi!</p>;
}
return (
<div>
<button onClick={send} disabled={status === "yuborilmoqda"}>
Buyurtma berish
</button>
{status === "yuborilmoqda" && <p>Yuborilmoqda…</p>}
</div>
);
}Ekranda:
Qabul qilindi!| Qadam | status |
Ekranda |
|---|---|---|
| boshida | "tahrir" |
Buyurtma berish |
| bosildi | "yuborilmoqda" |
tugma (bloklangan), Yuborilmoqda… |
| 500 ms dan keyin | "yuborildi" |
Qabul qilindi! |
Endi "ham yuborilmoqda, ham yuborildi" holatini yozib bo'lmaydi: status bir vaqtda faqat bitta qiymat. Tugmaning bloklanishi ham status'dan hisoblanadi (disabled={status === "yuborilmoqda"}) — alohida isDisabled state kerak emas.
Bunday qiymat ko'pincha status enum deyiladi: "enum" — oldindan sanab chiqilgan qiymatlar to'plami (enum va uning muqobillari darsida TypeScript'dagi shaklini ko'rgansiz). Boolean'lar hali ham kerak bo'lsa, ularni status'dan hisoblang: const isSending = status === "yuborilmoqda";.
5.3 TypeScript imkonsiz qiymatni to'xtatadi
JavaScript'da setStatus("yubordi") (harf xatosi) jim o'tadi va ekran hech qaysi shartga tushmaydi. TypeScript'da status turini literal union bilan yozamiz:
import { useState } from "react";
type Status = "tahrir" | "yuborilmoqda" | "yuborildi" | "xato";
export function App() {
const [status, setStatus] = useState<Status>("tahrir");
return (
<button onClick={() => setStatus("yubordi")}>{status}</button>
);
}Xato:
TS2345: Argument of type '"yubordi"' is not assignable to parameter of type 'SetStateAction<Status>'.""yubordi" turidagi argumentni SetStateAction<Status> turidagi parametrga berib bo'lmaydi." SetStateAction<Status> — setter qabul qiladigan narsa: yo Status qiymati, yo prev => Status funksiyasi (snapshot va updater darsida ko'rgan shakl). useState<Status> dagi <Status> — generic (Generics): "bu state faqat shu to'rtta satrdan biri".
Har statusga matn kerak bo'lsa, Record<Status, string> yozing — bittasini unutsangiz, tsc aytadi. vazifalar da aynan shunday: Record<Filtr, string> bilan bo'sh ro'yxat matnlari.
Tekshirib ko'ring: Rasm yuklash komponentida uchta boolean bor:
isLoading,isError,isDone. Nechta kombinatsiya mumkin va nechta haqiqiy holat bor?
Javob
Uch boolean — 2 × 2 × 2 = 8 ta kombinatsiya. Haqiqiy holatlar esa to'rtta: kutish (hech narsa boshlanmagan), yuklanmoqda, xato, tayyor. Qolgan to'rttasi (masalan "yuklanmoqda va xato") — imkonsiz holatlar. Bitta status: "kutish" | "yuklanmoqda" | "xato" | "tayyor" ularni yo'qotadi.
6. Chuqur ichma-ich tuzilmani tekislash
6.1 Ichma-ich menyu
«Bahor» menyusi bo'limlarga bo'lingan: issiq taomlar, ichimliklar. Birinchi xayolga keladigan tuzilma — ichma-ich:
const menu = [
{
id: "issiq",
title: "Issiq taomlar",
items: [
{ id: 1, name: "Osh" },
{ id: 2, name: "Manti" },
],
},
{
id: "ichimlik",
title: "Ichimliklar",
items: [{ id: 3, name: "Ko'k choy" }],
},
];
// Mantini o'chirish: avval bo'limni, keyin ichidagi taomni topish
function removeItem(menu, itemId) {
return menu.map((section) => ({
...section,
items: section.items.filter((it) => it.id !== itemId),
}));
}
const next = removeItem(menu, 2);
console.log(next[0].items.map((it) => it.name)); // [ 'Osh' ]
console.log(menu[0].items.length); // 2Ishlaydi, lekin har amal ikki qavat chuqurlikda: map ichida filter, ikki spread. Taomni bir bo'limdan boshqasiga ko'chirish yoki narxni o'zgartirish yanada uzun bo'ladi. Uch qavatli tuzilmada (bo'lim → taom → qo'shimcha) bu kod tez chalkashib ketadi. Yana bir xavf: bitta taom ikki bo'limda (masalan, "Tavsiya" va "Issiq taomlar") bo'lsa, u ikki nusxada saqlanadi — bu «Takrorlash» bo'limidagi muammo.
6.2 Tekis tuzilma
Tekis (normalized) tuzilma — har ma'lumot bitta ro'yxatda, bog'lanish esa id orqali. Taom o'z bo'limini sectionId bilan "eslaydi":
import { useState } from "react";
const SECTIONS = [
{ id: "issiq", title: "Issiq taomlar" },
{ id: "ichimlik", title: "Ichimliklar" },
];
const START = [
{ id: 1, name: "Osh", sectionId: "issiq" },
{ id: 2, name: "Manti", sectionId: "issiq" },
{ id: 3, name: "Ko'k choy", sectionId: "ichimlik" },
];
function App() {
const [items, setItems] = useState(START);
function remove(id) {
setItems(items.filter((it) => it.id !== id)); // bitta qavat
}
return SECTIONS.map((s) => (
<section key={s.id}>
<h3>{s.title}</h3>
{items
.filter((it) => it.sectionId === s.id)
.map((it) => (
<p key={it.id}>
{it.name}{" "}
<button onClick={() => remove(it.id)}>
{it.name} o'chirish
</button>
</p>
))}
</section>
));
}Ekranda:
Issiq taomlar
Osh Osh o'chirish
Ichimliklar
Ko'k choy Ko'k choy o'chirishO'chirish — bitta filter. Bo'limlar ro'yxati umuman o'zgarmaydi (u state ham emas — konstanta). Taomni ko'chirish — bitta maydonni almashtirish: { ...it, sectionId: "ichimlik" }. Ekrandagi guruhlash esa renderda hisoblanadi.
Katta ilovalarda tekis tuzilma ko'pincha { byId: { 1: {…}, 2: {…} }, ids: [1, 2] } ko'rinishida bo'ladi — id bo'yicha qidirish massivdagi find dan tez. State kutubxonalari bilan ishlaganda (Redux Toolkit II) shu shaklni ko'p uchratasiz. Kichik ilova uchun sectionId li massiv yetarli.
flowchart LR
subgraph I["Ichma-ich"]
B1["Bo'lim"] --> T1["Taom"]
B2["Bo'lim"] --> T2["Taom (nusxa)"]
end
subgraph T["Tekis"]
TT["Taomlar ro'yxati"] -- "sectionId" --> BB["Bo'limlar ro'yxati"]
endChapda bitta taom ikki joyda yashashi mumkin. O'ngda har taom bir marta saqlanadi, bog'lanish esa id bilan.
7. Vazifalar qadami: hisob va teglar — state emas
7.1 Oldin: DOM — xotira
vazifalar hozir (State III darsidagi holat) shunday ishlaydi: ro'yxat qatorlarini va yangi vazifa formasini React chizadi, qolgan qismni esa render.tsx dagi vanilla funksiyalar yozadi. Ular orasida — hisobniYoz, teglarniYoz, boshXabarniYoz, qidiruvniYoz. Har biri hisoblaydi va natijani DOM'ga yozadi. Bitta qiziq qator bor:
function boshXabarniYoz(): void {
boshXabar.hidden = royxat.children.length > 0;
// …
}"Ro'yxat bo'shmi?" savoliga DOM javob beradi: <ul> da nechta bola bor. Ya'ni ma'lumotning ikkinchi nusxasi — sahifaning o'zida. Shuning uchun bu funksiyani ro'yxat chizilgandan keyin chaqirish shart edi (render() ichida tartib muhim). Yana bir yordamchi — matnniYoz — bir xil matnni qayta yozmaslik uchun DOM'dagi matnni yangisi bilan solishtirardi. Ekran o'quvchi o'zgarmagan hisobni qayta o'qimasin, deb shunday qilingan.
7.2 Keyin: renderda hisoblanadi
Bu qism ikki darsda bitta o'zgarish bo'lib bajariladi. Bugun — reja va birinchi komponent; keyingi darsda — butun UI bitta App ostida va commit. Yangi branch oching: refactor/app-ildizi.
Hisob va teglar qatori uchun src/Statistika.tsx komponenti (kanondan aynan):
// Statistika.tsx — hisob va teglar qatori. Hammasi ro'yxatdan
// hisoblanadi (hosilaviy qiymat, 17/#15) — alohida saqlanmaydi
import { teglarMatni } from "./teglar.ts";
type Props = {
qoldi: number;
bajarildi: number;
teglar: ReadonlyMap<string, number>; // teg → nechta vazifada
// faqat bajarilganlarda qolgan teglar:
yopilgan: ReadonlySet<string>;
};
export function Statistika({
qoldi,
bajarildi,
teglar,
yopilgan,
}: Props) {
const qoshimcha =
yopilgan.size > 0 ? `. Yopilgan: ${[...yopilgan].join(", ")}` : "";
return (
<>
<p id="hisob" aria-live="polite">
{`${qoldi} ta vazifa qoldi, ${bajarildi} ta bajarildi`}
</p>
<p id="teglar" hidden={teglar.size === 0}>
{`Teglar: ${teglarMatni(teglar)}${qoshimcha}`}
</p>
</>
);
}Statistika hech narsani saqlamaydi — props'dan matn yasaydi. qoshimcha — yana bir hosilaviy qiymat. teglarMatni — teglar.ts dagi sof funksiya (render.tsx dan ko'chadi). aria-live="polite" — ARIA darsidagi jonli hudud: hisob o'zgarsa, Malikaning ekran o'quvchisi uni o'qib beradi.
Hisoblashning o'zi App da bo'ladi. Kelajakdagi App.tsx dan parcha (to'liq fayl — keyingi darsda):
// Hosilaviy qiymatlar (17/#15): har renderda vazifalardan
// hisoblanadi, state'da saqlanmaydi — "ro'yxat o'zgardi, hisob
// esa eski" degan xato bo'lishi mumkin emas
const royxat = VazifalarRoyxati.dan(vazifalar);
const korinadigan = korinadiganVazifalar(
vazifalar,
filtr,
saralash,
qidiruv,
);
const { qoldi, bajarildi } = royxat.statistika();
const teglar = royxat.tegHisobi();
const daraxt = tegDaraxtiniQur(teglar);Beshta hosilaviy qiymat, birortasi ham state emas. Endi "bo'sh xabar" ham DOM'ga emas, korinadigan.length ga qaraydi:
<p id="bosh-xabar" hidden={korinadigan.length > 0}>matnniYoz ham kerak bo'lmay qoladi. React matn tugunini faqat matn o'zgarganda yozadi — solishtirishni o'zi qiladi. Ekran o'quvchi o'zgarmagan hisobni qayta eshitmaydi.
7.3 Kanondagi boshqa qarorlar — shu dars ko'zi bilan
holat.ts dagi Holat turini ochib, bugungi qoidalarni qidiring:
export type Holat = {
vazifalar: readonly VazifaMalumoti[];
filtr: Filtr;
qidiruv: string;
saralash: Saralash;
tahrirId: number | null;
tarmoq: TarmoqHolati;
// …
};tahrirId: number | null— tahrirdagi vazifaning id si, obyekti emas. Fon yangilanishi (server, boshqa tab) vazifa matnini almashtirsa ham, "qaysi qator tahrirda" degan javob buzilmaydi.tarmoq: TarmoqHolati—"yuklanmoqda" | "tayyor" | "xato" | "oflayn". To'rtta boolean emas, bitta status.- Hisob, teglar, ko'rinadigan vazifalar —
Holatda yo'q. Ular hosilaviy.
Bu tur TypeScript qismida shunday yozilgan edi — React'ga o'tishda u o'zgarmadi. Yaxshi state tuzilmasi kutubxonaga bog'liq emas.
8. Ko'p uchraydigan xatolar
| Belgi | Sabab | Davosi |
|---|---|---|
| jami yoki hisob eskirib qoladi | hosilaviy qiymat alohida state'da | renderda hisoblang |
| bola komponent ota o'zgarishini ko'rmaydi | useState(props.x) — nusxa |
props'ni to'g'ridan-to'g'ri ishlating yoki initialX deb nomlang |
| tanlangan element eski ma'lumotni ko'rsatadi | obyekt nusxasi state'da | id saqlang, obyektni find bilan oling |
| ekranda ikki zid yozuv | ikki boolean flag | bitta status |
setStatus("yubordi") jim o'tadi |
JS'da tur yo'q | TS literal union: useState<Status> |
| yangilash kodi uch qavat spread | chuqur ichma-ich tuzilma | tekis ro'yxat + id bog'lanish |
9. Mashqlar
Mashqlar kurs/mashqlar/17/15-state-tuzilmasi/ da. Har yechimni «Ishga tushir» bilan sinang.
1-mashq (oson): Ortiqchasini toping
Kafe xodimi uchun smena paneli. Unda beshta state bor:
const [orders, setOrders] = useState([]); // buyurtmalar
const [orderCount, setOrderCount] = useState(0);
const [firstName, setFirstName] = useState("Sardor");
const [lastName, setLastName] = useState("Aliyev");
const [fullName, setFullName] = useState("Sardor Aliyev");- Qaysi state
ordersdan hisoblanadi: - Qaysi state ism va familiyadan hisoblanadi:
- Tuzatilgandan keyin nechta
useStateqoladi:
Yechim
orderCount — orders.length, fullName — `${firstName} ${lastName}`. Ikkalasi hosilaviy, renderda const bilan hisoblanadi. Uchta useState qoladi: orders, firstName, lastName.
2-mashq (o'rta): Navbatdagi stol
Ofitsiant ekranida stollar ro'yxati va "Hozir xizmatda" qatori bor. Har stolga "Xizmat" tugmasi (shu stolni tanlaydi) va "+1 mehmon" tugmasi bor. Sardor tanlangan stolni obyekt bilan saqladi va "+1 mehmon" dan keyin pastdagi qator eskirib qoladi. Kodni id bilan tuzating. Ishora: «Obyekt emas, id saqlang» bo'limidagi find.
const [tables, setTables] = useState([
{ id: 4, guests: 2 },
{ id: 7, guests: 5 },
]);
const [current, setCurrent] = useState(null);
// …
<p>
Hozir xizmatda:{" "}
{current ? `${current.id}-stol` : "hech kim"}
</p>Yechim
import { useState } from "react";
function App() {
const [tables, setTables] = useState([
{ id: 4, guests: 2 },
{ id: 7, guests: 5 },
]);
const [currentId, setCurrentId] = useState(null);
const current = tables.find((t) => t.id === currentId);
function addGuest(id) {
setTables(tables.map((t) =>
t.id === id ? { ...t, guests: t.guests + 1 } : t
));
}
return (
<div>
{tables.map((t) => (
<p key={t.id}>
{t.id}-stol: {t.guests} kishi{" "}
<button onClick={() => setCurrentId(t.id)}>
{t.id}-stol xizmat
</button>{" "}
<button onClick={() => addGuest(t.id)}>
{t.id}-stol +1
</button>
</p>
))}
<p>
Hozir xizmatda:{" "}
{current
? `${current.id}-stol, ${current.guests} kishi`
: "hech kim"}
</p>
</div>
);
}Ekranda:
4-stol: 3 kishi 4-stol xizmat 4-stol +1
7-stol: 5 kishi 7-stol xizmat 7-stol +1
Hozir xizmatda: 4-stol, 3 kishicurrentId — state, current — hosilaviy. Pastdagi qator har renderda ro'yxatdan olinadi, shuning uchun "3 kishi" darhol ko'rinadi.
3-mashq (qiyin): Bron formasining statusi
Stol bron qilish komponentida uchta boolean bor: isChecking (bo'sh joy tekshirilmoqda), isBooked (bron qilindi), isFull (joy yo'q). TypeScript'da bitta status bilan qayta yozing:
type BookingStatus— to'rtta qiymat:"tanlash","tekshirilmoqda","bron","joy-yoq";Record<BookingStatus, string>bilan har holat matni;- «Bron qilish» tugmasi
"tekshirilmoqda"da bloklangan bo'lsin (disabled— hosilaviy); - tugma bosilganda 300 ms dan keyin: mehmonlar soni 8 dan ko'p bo'lsa —
"joy-yoq", aks holda"bron". Mehmonlar soni —const guests = 4;konstanta.
Ishora: Record<K, V> — Index signature va Record darsida; setTimeout — status ikki marta o'zgaradi.
Yechim
import { useState } from "react";
type BookingStatus =
| "tanlash"
| "tekshirilmoqda"
| "bron"
| "joy-yoq";
const MESSAGES: Record<BookingStatus, string> = {
tanlash: "Stol tanlang",
tekshirilmoqda: "Bo'sh joy tekshirilmoqda…",
bron: "Stol bron qilindi!",
"joy-yoq": "Afsuski, joy yo'q",
};
const guests = 4;
export default function App() {
const [status, setStatus] =
useState<BookingStatus>("tanlash");
const isChecking = status === "tekshirilmoqda"; // hosilaviy
function book() {
setStatus("tekshirilmoqda");
setTimeout(() => {
setStatus(guests > 8 ? "joy-yoq" : "bron");
}, 300);
}
return (
<div>
<p>{guests} kishi uchun</p>
<button onClick={book} disabled={isChecking}>
Bron qilish
</button>
<p>{MESSAGES[status]}</p>
</div>
);
}Ekranda:
4 kishi uchun
Bron qilish
Stol bron qilindi!Uch boolean'dagi sakkizta kombinatsiya o'rniga to'rtta ma'noli holat. MESSAGES dan bitta kalitni o'chirsangiz, tsc TS2741 (Property '"joy-yoq"' is missing…) bilan to'xtatadi.
4-mashq: Vazifalar qadami
vazifalar da refactor/app-ildizi branch'ini oching. «Vazifalar qadami» bo'limidagidek src/Statistika.tsx ni yarating va render.tsx dagi hisobniYoz va teglarniYoz hisoblayotgan qiymatlarni yozib chiqing: qaysilari holat dan, qaysilari hosilaviy. Commit hozircha qilinmaydi — komponent keyingi darsda App ga ulanadi.
Yechim
Ro'yxat: qoldi, bajarildi (royxat.statistika()), teglar (royxat.tegHisobi()), yopilgan (royxat.yopilganTeglar()), qoshimcha matn — hammasi holat.vazifalar dan hisoblanadi. holat dan to'g'ridan-to'g'ri faqat vazifalar olinadi. npm run tip — Statistika.tsx hali hech qayerda import qilinmagan bo'lsa ham tip xatosiz bo'lishi kerak. knip esa "ishlatilmagan fayl" deydi — bu keyingi darsda yo'qoladi.
10. Real ishda
- Kod ko'rigida (code review) eng ko'p yoziladigan izohlardan biri: "Bu state kerak emas, hisoblasa bo'ladi". Ayniqsa effekt bilan state'ni sinxronlash (
useEffect(() => setTotal(…))) — bu ham ortiqcha state, buni Sizga effekt kerak emas darsida ko'ramiz. - Status maydonlari — server bilan ishlaydigan har komponentda:
"idle" | "loading" | "success" | "error". TanStack Query kabi kutubxonalar ham aynan shundaystatusqaytaradi (19-qism). - Normalizatsiya — Redux Toolkit'ning
createEntityAdapteri va ma'lumotlar bazasi (SQL'dagi jadval va tashqi kalit) shu g'oyaga qurilgan. Holat mashinasi fikrlashi — Reducer + Immer darsida. - Intervyu savollari: "Derived state nima va nega uni saqlamaslik kerak?", "Props'ni state'ga ko'chirish nega xavfli?", "
isLoadingvaisErrorni bitta qiymat bilan qanday ifodalaysiz?".
Xulosa
- State — faqat o'zgaradigan va hisoblab bo'lmaydigan ma'lumot; qolgani renderda hisoblanadi.
useState(props.x)faqat birinchi qiymatni oladi; nusxa ataylab bo'lsa —initialXdeb nomlang.- Tanlangan element — id; obyektning o'zi
findbilan har renderda topiladi. - Bir-biriga zid bo'lishi mumkin bo'lgan boolean'lar o'rniga bitta status; TS literal union imkonsiz qiymatni to'sadi.
- Chuqur ichma-ich tuzilma o'rniga tekis ro'yxat va id bog'lanish.
vazifalarda hisob, teglar va ko'rinadigan vazifalar —Apprenderida hisoblanadi, DOM'dan o'qilmaydi.
Keyingi dars: State'ni ko'tarish va yagona haqiqat manbai — ikki komponent bir ma'lumotni ulashsa, state qayerda turishi kerak; vazifalar ning butun UI'si bitta App ostiga yig'iladi.
Manbalar
- React: "Choosing the State Structure" — react.dev/learn/choosing-the-state-structure
- React: "Thinking in React" (3-qadam: minimal state) — react.dev/learn/thinking-in-react
- React:
useState— react.dev/reference/react/useState
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!