Mundarija (31)
- Bu darsda
- 1. Nega bu kerak?
- 2. map bilan ro'yxat
- 2.1 Massivdan elementlar massivi
- 2.2 key — map dagi eng tashqi elementga
- 2.3 filter + map
- 3. key qoidalari
- 3.1 Qanday qiymat key bo'ladi
- 3.2 Takroriy key
- 3.3 key props'ga kirmaydi
- 4. Index key nega xato beradi
- 4.1 Tuzoqni jonli ko'ramiz
- 4.2 Nega shunday bo'ldi
- 4.3 Tuzatish — id
- 4.4 Index qachon mumkin
- 5. Renderda tasodifiy key — crypto.randomUUID()
- 6. key va TypeScript
- 7. Vazifalar qadami: ro'yxatni React chizadi
- 7.1 VazifaRoyxati
- 7.2 render.ts → render.tsx
- 7.3 Birinchi komponent testlari: Vitest
- 7.4 Build, brauzer tekshiruvi va commit
- 8. Ko'p uchraydigan xatolar
- 9. Mashqlar
- 1-mashq (oson): key'ni toping
- 2-mashq (o'rta): menyu bo'limlari
- 3-mashq (qiyin): index key xatosini tuzating (TypeScript)
- 4-mashq: Vazifalar qadami
- 10. Real ishda
- Xulosa
- Manbalar
Ro'yxatlarni render qilish va key: map, filter va nega index key xato beradi
Qisqacha: React'da ro'yxat massivdan chiziladi:
{dishes.map((d) => <MenuItem key={d.id} {...d} />)}. Har elementgakeykerak — u React'ga "bu qaysi taom" ekanini aytadi. Ro'yxat o'zgarganda React elementlarni tartib raqami bo'yicha emas,keybo'yicha taniydi.keyqardoshlar orasida noyob va barqaror bo'lsin — ma'lumotning o'z id'si. Massiv indeksini key qilsangiz, o'chirish yoki saralashda yozilgan matn boshqa qatorga "ko'chib" qoladi. Renderdacrypto.randomUUID()esa har chizishda hamma qatorni yangidan yasaydi.
Bu darsda
- Massivni
mapbilan JSX ro'yxatiga,filter+mapbilan saralangan ro'yxatga aylantirasiz. keyni qayerga va qanday qiymat bilan qo'yishni bilasiz.- Index key qanday xatoga olib kelishini jonli ko'rib, uni tushuntira olasiz.
- Renderda tasodifiy key yasash nega xavfli ekanini bilasiz.
vazifalarro'yxatini React'ga berasiz va birinchi komponent testlarini ishga tushirasiz.
Oldin bilishingiz kerak: Shartli render, map: har elementni o'zgartirish, filter: shart bo'yicha tanlash, JSX II: ifodalar, Fragment va nima ekranga chiqadi.
1. Nega bu kerak?
Haqiqiy ilovada deyarli hamma narsa ro'yxat: menyu, buyurtmalar, izohlar, vazifalar. Ma'lumot serverdan massiv bo'lib keladi. Taomlar soni bugun 12 ta, ertaga 15 ta — JSX'da qo'lda yozib bo'lmaydi.
vazifalar v6 da ro'yxat mana bunday chizilardi:
royxat.replaceChildren(...korinadiganlar().map(qatorYasa));Har o'zgarishda hamma <li> o'chirilib, qaytadan yasalardi. Bitta katakni belgilasangiz ham — 1 000 qator noldan. React nima va nega darsida o'lchagan raqamni eslang: 1 000 vazifada bitta katak 47–77 ms. React esa faqat o'zgargan qatorga tegadi — 19–20 ms. Lekin buning uchun React har qatorni tanib olishi kerak: "eski ro'yxatdagi 2-qator yangi ro'yxatda qayerda?". Bugungi darsning yuragi — shu tanib olish, ya'ni key.
Hayotiy o'xshatish: kinoteatrdagi chiptalar. O'rindiqlar raqamlangan, chiptada raqam yozilgan. Kimdir chiqib ketsa ham, qolganlar o'z o'rindig'ida o'tiradi. Agar odamni "qatorda nechanchi turgani" bo'yicha tanisangiz edi, bitta odam chiqqanda hamma bir o'rin surilardi — va hamma birovning o'rnida o'tirib qolardi.
2. map bilan ro'yxat
2.1 Massivdan elementlar massivi
JSX II darsida ko'rgandik: massiv {} ichida chizilsa, elementlari ketma-ket chiqadi. map esa ma'lumot massivini element massiviga aylantiradi:
const menu = [
{ id: 1, name: "Osh", price: 35000 },
{ id: 2, name: "Lag'mon", price: 28000 },
{ id: 3, name: "Manti", price: 30000 },
];
function MenuItem({ name, price }) {
return (
<li>
{name} — {price} so'm
</li>
);
}
function App() {
return (
<ul>
{menu.map((dish) => (
<MenuItem key={dish.id} name={dish.name} price={dish.price} />
))}
</ul>
);
}Ekranda:
Osh — 35000 so'm
Lag'mon — 28000 so'm
Manti — 30000 so'mBosqichma-bosqich: menu.map(...) uchta <MenuItem …/> elementini qaytaradi → [el1, el2, el3] massivi <ul> ichiga tushadi → React ularni ketma-ket chizadi. {...dish} spread (Props) bilan qisqaroq ham yoziladi: <MenuItem key={dish.id} {...dish} />.
2.2 key — map dagi eng tashqi elementga
Diqqat qiling: key <li> ga emas, <MenuItem> ga qo'yildi. Qoida: key — map qaytaradigan eng tashqi elementda. React qardoshlarni solishtiradi — bu yerda qardoshlar MenuItem lar. <li> esa har MenuItem ning ichida yolg'iz.
Ko'p uchraydigan xato — key ni komponent ichiga qo'yish:
function MenuItem({ name }) {
return <li key={name}>{name}</li>; // ❌ bu yerda foydasiz
}
function App() {
const dishes = ["Osh", "Manti"];
return (
<ul>
{dishes.map((d) => (
<MenuItem name={d} />
))}
</ul>
);
}Konsolda:
Each child in a list should have a unique "key" prop.
Check the render method of `App`. See https://react.dev/link/warning-keys for more information.Ekranda:
Osh
Manti"Ro'yxatdagi har bolada noyob key bo'lishi kerak. App ning render metodini tekshiring." React to'g'ri joyni ko'rsatdi: muammo App da, map qatorida. MenuItem ichidagi key — boshqa ro'yxatda, u yerda qardosh yo'q.
2.3 filter + map
Ro'yxatning bir qismi kerak bo'lsa — avval filter, keyin map (metod zanjiri):
const menu = [
{ id: 1, name: "Osh", price: 35000, soldOut: false },
{ id: 2, name: "Somsa", price: 8000, soldOut: true },
{ id: 3, name: "Manti", price: 30000, soldOut: false },
{ id: 4, name: "Ko'k choy", price: 5000, soldOut: false },
];
function App() {
const available = menu.filter((dish) => !dish.soldOut);
const cheapFirst = available.toSorted((a, b) => a.price - b.price);
return (
<>
<p>Bugun {available.length} ta taom bor:</p>
<ol>
{cheapFirst.map((dish) => (
<li key={dish.id}>
{dish.name} — {dish.price} so'm
</li>
))}
</ol>
</>
);
}Ekranda:
Bugun 3 ta taom bor:
Ko'k choy — 5000 so'm
Manti — 30000 so'm
Osh — 35000 so'mIkki narsa muhim. Birinchisi: hisob-kitob (filter, toSorted) return dan oldin, oddiy o'zgaruvchilarda. JSX'da faqat chizish qoldi — o'qish oson. Ikkinchisi: sort emas, toSorted (Nusxa bilan o'zgartirish). sort asl massivni joyida o'zgartiradi — menu ni ham. Komponent tashqaridagi ma'lumotni buzmasligi kerak (Pure komponentlar darsida nega). id lar tartib o'zgarsa ham taomga "yopishib" qoldi: Ko'k choy — key={4}, qayerda turmasin.
Ro'yxat bo'sh bo'lishi mumkinligini unutmang: Shartli render darsidagi "bo'sh holat" — available.length === 0 bo'lsa, <ol> o'rniga xabar.
Tekshirib ko'ring:
{menu.map((dish) => { <li key={dish.id}>{dish.name}</li> })}— ekranda nima chiqadi?
Javob
Hech narsa. Jingalak qavsli arrow funksiya return siz undefined qaytaradi — map [undefined, undefined, …] massivini beradi, React esa undefined ni chizmaydi. Komponentlar darsidagi return unutilgan xatoning o'zi. Tuzatish: oddiy qavs (dish) => (<li …>…</li>) yoki { return <li …/>; }.
3. key qoidalari
3.1 Qanday qiymat key bo'ladi
key — satr yoki son. Uch talab:
| Talab | Ma'nosi | Buzilsa |
|---|---|---|
| Noyob | qardoshlar orasida takrorlanmaydi | ogohlantirish, qator yo'qolishi |
| Barqaror | bir element uchun har chizishda bir xil | qator har safar qayta yasaladi |
| Ma'lumotdan | elementning o'zi bilan bog'liq (id) | o'chirishda qiymatlar "ko'chadi" |
Eng yaxshi key — ma'lumotning o'z id'si: bazadagi id, vazifalar dagi vazifa.id, mahsulot kodi. Noyoblik faqat qardoshlar orasida kerak: ikki xil ro'yxatda bir xil key bo'lishi mumkin.
3.2 Takroriy key
Ikki taomga xato bilan bir xil id berilgan:
function App() {
const dishes = [
{ id: 1, name: "Osh" },
{ id: 1, name: "Manti" },
];
return (
<ul>
{dishes.map((d) => (
<li key={d.id}>{d.name}</li>
))}
</ul>
);
}Konsolda:
Encountered two children with the same key, `1`. Keys should be unique so that components maintain their identity across updates. Non-unique keys may cause children to be duplicated and/or omitted — the behavior is unsupported and could change in a future version.Ekranda:
Osh
Manti"Bir xil 1 kalitli ikki bola topildi. Kalitlar noyob bo'lishi kerak — shunda komponentlar yangilanishlar orasida o'zligini saqlaydi. Noyob bo'lmagan kalitlar bolalarni takrorlashi yoki tushirib qoldirishi mumkin." Hozir ekran to'g'ri, lekin bu tasodif: ro'yxat o'zgarganda bitta qator ikki marta chiqishi yoki yo'qolishi mumkin. Odatda bunday xato ma'lumotning o'zida — server ikki xil yozuvga bir id bergan. Uni key'ni "boshqacha qilib" emas, ma'lumot manbasida tuzatish kerak.
3.3 key props'ga kirmaydi
Props darsida ko'rgandik: key komponentga yetib bormaydi, props.key — undefined va ogohlantirish. Id komponentning ichida ham kerak bo'lsa — ikki marta bering: <MenuItem key={dish.id} id={dish.id} />. Bu takror emas: birinchisi React uchun, ikkinchisi sizning kodingiz uchun.
4. Index key nega xato beradi
4.1 Tuzoqni jonli ko'ramiz
Eng ko'p uchraydigan "yechim": "id yo'q ekan — indeksni beraman". map ning ikkinchi argumenti — index (Callback imzosi). Ogohlantirish yo'qoladi, lekin xato qoladi. Ko'ramiz: har taom yonida izoh maydoni bor ("piyozsiz", "achchiq"). Blok "Osh izohi" ga "piyozsiz" deb yozadi, keyin oshni ro'yxatdan o'chiradi:
import { flushSync } from "react-dom";
import { createRoot } from "react-dom/client";
const root = createRoot(document.getElementById("root"));
let dishes = [
{ id: 1, name: "Osh" },
{ id: 2, name: "Manti" },
{ id: 3, name: "Lag'mon" },
];
function DishRow({ name }) {
return (
<li>
{name}: <input aria-label={`${name} izohi`} />
</li>
);
}
function draw() {
root.render(
<>
<ul>
{dishes.map((dish, index) => (
<DishRow key={index} name={dish.name} /> // ❌ index
))}
</ul>
<button onClick={removeFirst}>Oshni o'chirish</button>
</>
);
}
function removeFirst() {
dishes = dishes.slice(1);
flushSync(draw);
for (const input of document.querySelectorAll("input")) {
const label = input.getAttribute("aria-label");
console.log(label, JSON.stringify(input.value));
}
}
draw();Konsolda:
Manti izohi "piyozsiz"
Lag'mon izohi ""Ekranda:
Manti:
Lag'mon:
Oshni o'chirishOsh o'chdi, lekin uning "piyozsiz" izohi mantiga o'tib qoldi. Oshpaz piyozsiz manti tayyorlaydi — mijoz esa bunday so'ramagan edi. «Ishga tushir» bilan o'zingiz sinang: istalgan izohni yozib, oshni o'chiring.
4.2 Nega shunday bo'ldi
React eski va yangi ro'yxatni key bo'yicha juftlaydi:
| key | Oldin | Keyin | React qarori |
|---|---|---|---|
0 |
Osh, input: "piyozsiz" | Manti | "0-element qoldi, nomi o'zgardi" — input'ga tegmaydi |
1 |
Manti, input: "" | Lag'mon | "1-element qoldi, nomi o'zgardi" |
2 |
Lag'mon, input: "" | — | "2-element yo'qoldi" — oxirgisini o'chiradi |
React nuqtai nazaridan hech kim o'chirilmadi — oxirgisi o'chirildi, qolganlarning matni yangilandi. Matn props'dan keladi va to'g'ri yangilandi. <input> dagi yozuv esa DOM elementining o'zida turadi — u key 0 li elementda qoldi. Endi o'sha elementda "Manti" yozilgan.
Diagrammada: chapda eski ro'yxat, o'ngda yangi. Chiziqlar React qaysi elementni qaysi biriga moslaganini ko'rsatadi:
flowchart LR
subgraph Oldin
A0["key 0: Osh + piyozsiz"]
A1["key 1: Manti"]
A2["key 2: Lag'mon"]
end
subgraph Keyin
B0["key 0: Manti + piyozsiz"]
B1["key 1: Lag'mon"]
end
A0 --> B0
A1 --> B1
A2 -. "o'chirildi" .-> X["—"]4.3 Tuzatish — id
Bitta o'zgarish: key={index} o'rniga key={dish.id}:
import { flushSync } from "react-dom";
import { createRoot } from "react-dom/client";
const root = createRoot(document.getElementById("root"));
let dishes = [
{ id: 1, name: "Osh" },
{ id: 2, name: "Manti" },
{ id: 3, name: "Lag'mon" },
];
function DishRow({ name }) {
return (
<li>
{name}: <input aria-label={`${name} izohi`} />
</li>
);
}
function draw() {
root.render(
<>
<ul>
{dishes.map((dish) => (
<DishRow key={dish.id} name={dish.name} /> // ✅ id
))}
</ul>
<button onClick={removeFirst}>Oshni o'chirish</button>
</>
);
}
function removeFirst() {
dishes = dishes.slice(1);
flushSync(draw);
for (const input of document.querySelectorAll("input")) {
const label = input.getAttribute("aria-label");
console.log(label, JSON.stringify(input.value));
}
}
draw();Konsolda:
Manti izohi ""
Lag'mon izohi ""Ekranda:
Manti:
Lag'mon:
Oshni o'chirishEndi React ko'radi: "key 1 (Osh) yo'qoldi, 2 va 3 qoldi". Osh qatori izohi bilan birga o'chdi, qolganlariga tegilmadi. Xuddi o'rindiq raqamli chiptalar kabi.
4.4 Index qachon mumkin
Index key xato har doim emas, faqat ro'yxat o'zgarganda chiqadi. Uchala shart bajarilsa — index ham bo'ladi:
- ro'yxat hech qachon tartibi o'zgarmaydi (saralash, o'rtadan qo'shish yo'q);
- o'rtadan elementi o'chirilmaydi va filtrlanmaydi;
- elementlarda o'z holati yo'q (input, ochiq-yopiqlik, keyinchalik state).
Masalan, qadamlar ro'yxati ("1. Guruchni yuving, 2. …") — statik matn. Lekin "keyin hech qachon o'zgarmaydi" degan va'dani berish qiyin. Odat qiling: id bo'lsa — id. Id yo'q bo'lsa — uni ma'lumot yaratilgan paytda bering (pastda).
Tekshirib ko'ring: Index key bilan ro'yxatning oxiriga yangi taom qo'shilsa, izoh "ko'chadimi"? Boshiga qo'shilsa-chi?
Javob
Oxiriga qo'shilsa — yo'q: eski elementlar o'z indekslarini saqlaydi (0, 1, 2), yangisi 3 oladi. Boshiga qo'shilsa — ha: hamma indeks bittaga suriladi, yangi taom 0 ni oladi va eski oshning izohi unga "ko'chadi". Shuning uchun index key bilan yozilgan kod uzoq vaqt ishlab, birinchi "boshiga qo'shish" yoki saralashda buziladi.
5. Renderda tasodifiy key — crypto.randomUUID()
Index yomon ekanini bilgan dasturchi ba'zan teskarisini qiladi:
import { flushSync } from "react-dom";
import { createRoot } from "react-dom/client";
const root = createRoot(document.getElementById("root"));
const dishes = [{ id: 1, name: "Osh" }];
function draw() {
root.render(
<>
<ul>
{dishes.map((dish) => (
// ❌ har chizishda yangi key
<li key={crypto.randomUUID()}>
{dish.name}: <input aria-label={`${dish.name} izohi`} />
</li>
))}
</ul>
<button onClick={redraw}>Qayta chizish</button>
</>
);
}
function redraw() {
flushSync(draw);
const input = document.querySelector("input");
console.log("Izoh:", JSON.stringify(input.value));
}
draw();Konsolda:
Izoh: ""Ekranda:
Osh:
Qayta chizishRo'yxat o'zgarmadi — shunchaki qayta chizildi. Lekin izoh yo'qoldi. crypto.randomUUID() — brauzer va Node'dagi tasodifiy noyob id (UUID) — har chaqiruvda yangi qiymat beradi. Har chizishda key yangi — React "eski element yo'qoldi, yangisi keldi" deb o'ylaydi va har qatorni o'chirib, qaytadan yasaydi. Bu Komponentlar darsidagi "ichma-ich komponent" tuzog'ining aynan o'zi: barqarorlik yo'q.
Bu ikki marta zararli: matn va fokus yo'qoladi, va har chizishda butun ro'yxat qayta quriladi — v6 dagi replaceChildren ga qaytdik. Tuzatish: id'ni ma'lumot yaratilganda bering, chizishda emas:
const dishes = [];
function addDish(name) {
// ✅ id bir marta, qo'shilayotganda beriladi va saqlanadi
dishes.push({ id: crypto.randomUUID(), name });
}
addDish("Osh");
addDish("Manti");
console.log(dishes.length, dishes[0].id === dishes[0].id); // 2 true
console.log(typeof dishes[1].id); // stringvazifalar da ham shunday: vazifa qo'shilganda id oladi va u localStorage da, serverda — hamma joyda vazifa bilan birga yuradi.
6. key va TypeScript
key ning turi — Key (@types/react dan): string | number | bigint. Shuning uchun obyektni yoki undefined bo'lishi mumkin bo'lgan qiymatni key qilib bo'lmaydi:
type Dish = { id?: number; name: string };
const dishes: Dish[] = [{ id: 1, name: "Osh" }];
export function App() {
return (
<ul>
{dishes.map((dish) => (
<li key={dish}>{dish.name}</li>
))}
</ul>
);
}Xato:
TS2322: Type 'Dish' is not assignable to type 'Key | null | undefined'."Dish turini Key | null | undefined ga berib bo'lmaydi." Obyekt kalit bo'la olmaydi — dish.id kerak edi. E'tibor bering: undefined ruxsat etilgan. Ya'ni id?: number bo'lsa ham, key={dish.id} ni TypeScript o'tkazib yuboradi. Ma'lumotda id yo'q bo'lsa, React key'siz ogohlantirish beradi. Shuning uchun ro'yxat elementi turida id majburiy bo'lsin: vazifalar dagi VazifaMalumoti da readonly id: number.
7. Vazifalar qadami: ro'yxatni React chizadi
Besh darslik ishning oxirgi qadami. Komponentlar darsidan beri yozgan fayllar bugun ishga tushadi va commit bo'ladi. Kanon fayllaridagi uzun izoh qatorlari telefon ekraniga sig'ishi uchun qayta bo'lingan — mazmuni o'zgarmagan.
7.1 VazifaRoyxati
src/VazifaRoyxati.tsx:
// VazifaRoyxati.tsx — ro'yxat qatorlari (17/#09). <ul id="royxat">
// hali index.html da — React uning ICHINI chizadi (render.tsx)
import { VazifaQatori } from "./VazifaQatori.tsx";
import type { VazifaMalumoti } from "./turlar.ts";
type Props = {
vazifalar: readonly VazifaMalumoti[];
sorov: string;
tahrirId: number | null;
};
export function VazifaRoyxati({ vazifalar, sorov, tahrirId }: Props) {
// key — vazifa id: barqaror va takrorlanmaydi. Tartib o'zgarsa
// (saralash, bajarilgan pastga tushdi) React <li> ni yangidan
// yasamaydi, joyini almashtiradi — fokus va yozilgan matn
// saqlanadi
return vazifalar.map((vazifa) => (
<VazifaQatori
key={vazifa.id}
vazifa={vazifa}
sorov={sorov}
tahrirda={vazifa.id === tahrirId}
/>
));
}return vazifalar.map(...)— komponent massiv qaytaradi,<ul>siz.<ul id="royxat">index.htmlda qoladi (e2e va CSS unga qaraydi), React esa uning ichini chizadi. Komponent massiv qaytara oladi — Komponentlar darsidagi qoida.key={vazifa.id}— izohda yozilganidek: vazifalar saralanadi, bajarilgani pastga tushadi, filtr o'zgaradi. Hammasida React qatorni id bo'yicha taniydi. Tahrir maydonidagi yozuv "ko'chib" ketmaydi — «Index key nega xato beradi» bo'limidagi xato bu yerda bo'lishi mumkin emas.tahrirda={vazifa.id === tahrirId}— hisoblangan boolean.VazifaQatoriglobal holatni bilmaydi, unga tayyor javob beriladi (Shartli render).readonly VazifaMalumoti[]— komponent ro'yxatni faqat o'qiydi.pushyozilsa,tscto'xtatadi (Massiv va tuple turlari).
7.2 render.ts → render.tsx
Endi vanilla render() ga ro'yxatni React'ga topshirishni o'rgatamiz. Fayl JSX oladi — kengaytmasi .tsx bo'ladi (git mv src/render.ts src/render.tsx). asosiy.ts dagi import ham "./render.tsx" ga o'zgaradi. Asosiy o'zgarishlar:
-// render.ts — holatdan ekranga: chizish, fokus, e'lon
-import { Vazifa } from "./vazifa.ts";
+// render.tsx — holatdan ekranga: chizish, fokus, e'lon. Ro'yxat
+// qatorlarini endi React chizadi (17/#09), qolgani — hali vanilla
+import { StrictMode } from "react";
+import { flushSync } from "react-dom";
+import { createRoot } from "react-dom/client";
+import { VazifaRoyxati } from "./VazifaRoyxati.tsx";
+import { TAHRIR_MAYDONI_ID } from "./TahrirQatori.tsx";
…
-export const TAHRIR_MAYDONI_ID = "tahrir-maydon";
+export { TAHRIR_MAYDONI_ID };Keyin 84 qatorlik vanilla kod o'chadi: tugmaYasa va uning to'rtta .bind nusxasi, belgilanganMatn, oddiyQator, tahrirQatori, qatorYasa. Ularning o'rnini olti komponent egalladi. O'rniga uch qator:
// <ul id="royxat"> ichi — React ildizi (root). Ichini endi faqat
// React o'zgartiradi: replaceChildren yoki append bu yerda
// ishlatilmaydi
const royxatIldizi = createRoot(royxat);Va render() ning birinchi qatori:
export function render(): void {
- royxat.replaceChildren(...korinadiganlar().map(qatorYasa));
+ // root.render() navbatga qo'yadi, DOM keyinroq yangilanadi.
+ // flushSync — hozir, shu qatorda: pastdagi kod (bosh xabar,
+ // qidiruv natijasi, fokus) yangi qatorlarga qaraydi
+ flushSync(() => {
+ royxatIldizi.render(
+ <StrictMode>
+ <VazifaRoyxati
+ vazifalar={korinadiganlar()}
+ sorov={holat.qidiruv}
+ tahrirId={holat.tahrirId}
+ />
+ </StrictMode>,
+ );
+ });
// Ro'yxatdan keyin: bosh xabar qatorlar soniga qaraydi
boshXabarniYoz();Bu — vazifalar dagi ikkinchi React orol (Birinchi React loyiha dagi #izoh birinchisi edi). createRoot bir marta chaqiriladi, royxatIldizi.render(...) esa har holat o'zgarishida. Bu JSX II dagi draw() ning aynan o'zi: holat o'zgardi → yangi JSX → React farqni topadi.
flushSync nega kerak. root.render() chizishni darhol bajarmaydi — navbatga qo'yadi. render() ning pastki qatorlari esa yangi qatorlarga qaraydi. boshXabarniYoz() "ro'yxat bo'sh" xabarini qatorlar soniga qarab chiqaradi, fokus funksiyasi yangi tugmani qidiradi. flushSync — "chizishni hozir, shu qatorda tugat". Bu kam ishlatiladigan vosita; nega u odatda kerak emasligini State II darsida ko'ramiz. Bizda u vaqtinchalik ko'prik — vanilla va React bir ekranda yashayotgan payt uchun.
Holat va hodisalar hali vanilla. Katakni bosish, o'chirish, tahrirlash — asosiy.ts dagi delegatsiya tinglovchisi data-amal ga qarab ushlaydi. React chizgan <button data-amal="ochir"> ham oddiy DOM tugmasi — delegatsiya uni farqlamaydi. Bular Hodisalarni boshqarish darsida React'ga o'tadi.
7.3 Birinchi komponent testlari: Vitest
16-qismda sof mantiq testlari node:test da edi: tekshiruv/*.test.js, build'siz. Komponentni sinash uchun esa DOM kerak, Node'da esa DOM yo'q. Shuning uchun ikkinchi test qavati qo'shiladi. Vitest — Vite asosidagi test vositasi. jsdom — Node ichidagi "brauzer o'rinbosari". Testing Library — komponentni chizib, elementni foydalanuvchi kabi (roli va nomi bilan) topish kutubxonasi. Bularni chuqur Vitest I va React Testing Library I darslarida o'rganamiz. Bugun — faqat ulash va o'qish.
npm i -D vitest@5.0.4 jsdom@30.1.2 \
@testing-library/react@16.3.3 @testing-library/dom@10.4.2added 62 packages, and audited 327 packages in 19s
98 packages are looking for funding
run `npm fund` for details
found 0 vulnerabilitiespackage.json da "test": "node --test && vitest run" — ikki qavat ketma-ket. Yangi vitest.config.ts:
// vitest.config.ts — komponent testlari (17/#09). Sof mantiq
// testlari node:test da qoladi (tekshiruv/*.test.js, build'siz,
// tez); React komponentlari uchun DOM kerak — Vitest + jsdom
// (brauzer o'rinbosari) + Testing Library. vite.config.ts
// ishlatilmaydi: u .env.<mode> ni talab qiladi, testga esa faqat
// JSX kerak
import { defineConfig } from "vitest/config";
import react from "@vitejs/plugin-react";
export default defineConfig({
plugins: [react()],
test: {
environment: "jsdom",
// Faqat .tsx: tekshiruv/*.test.js — node:test fayllari, Vitest
// ularni o'qisa "node:test" importida yiqiladi
include: ["tekshiruv/**/*.test.tsx"],
setupFiles: ["tekshiruv/vitest-sozlash.ts"],
},
});Yana uchta kichik fayl: tekshiruv/vitest-sozlash.ts (har testdan keyin chizilganni tozalaydi — afterEach(cleanup)), tsconfig.test.json (test fayllari uchun uchinchi tsconfig) va tsconfig.json dagi references ga shu fayl. Hammasi kanonda izohlari bilan.
Test faylidan bitta misol — tekshiruv/VazifaRoyxati.test.tsx:
test("foydalanuvchi matni HTML bo'lib qolmaydi", () => {
const { container } = render(
<ul>
<VazifaRoyxati
vazifalar={[
{
id: 7,
matn: "<img src=x onerror=alert(1)>",
bajarildi: false,
},
]}
sorov=""
tahrirId={null}
/>
</ul>,
);
expect(container.querySelector("img")).toBeNull();
expect(container.querySelector("label")?.textContent).toBe(
"<img src=x onerror=alert(1)>",
);
});O'qib ko'ring: vazifa matni — XSS urinishi (JSX II dagi "Hujumchi nigohi"). Test ikki narsani tekshiradi: DOM'da <img> yo'q va yorliq matni harfma-harf o'sha satr. Qolgan to'rtta test: har vazifaga katak, yorliq va ikki tugma; bajarilgan qator klassi; qidiruvda <mark>; tahrir qatori.
npm testBirinchi ishga tushirish sekin — jsdom muhiti yuklanadi (bizda 67 s, Windows'da antivirus ham sekinlashtiradi). Keyingilari 3–5 s. Chiqishning Vitest qismi (--reporter=verbose bilan). Ikki narsaga qarang: har qator boshidagi (test o'tdi) va oxirgi ikki qatordagi sonlar:
✓ tekshiruv/VazifaRoyxati.test.tsx > VazifaRoyxati > har vazifaga qator: katak, yorliq, ikki tugma 151ms
✓ tekshiruv/VazifaRoyxati.test.tsx > VazifaRoyxati > bajarilgan: katak belgilangan, qator klassi 12ms
✓ tekshiruv/VazifaRoyxati.test.tsx > VazifaRoyxati > qidiruvga mos bo'lak <mark> ichida, yorliq matni to'liq 14ms
✓ tekshiruv/VazifaRoyxati.test.tsx > VazifaRoyxati > tahrirdagi qator: maydon va Saqlash/Bekor qilish 15ms
✓ tekshiruv/VazifaRoyxati.test.tsx > VazifaRoyxati > foydalanuvchi matni HTML bo'lib qolmaydi 7ms
Test Files 1 passed (1)
Tests 5 passed (5)node:test qismi o'zgarmadi: ℹ tests 146 · ℹ pass 146. Sof mantiq React'dan bexabar — ro'yxat qanday chizilishi uni qiziqtirmaydi.
7.4 Build, brauzer tekshiruvi va commit
npm run build — kirish chunk'i 245.21 kB (gzip 78.13). Birinchi React loyiha dagi orol bilan 244.82 kB edi. Olti komponent atigi 0.4 kB qo'shdi: React'ning asosiy narxi bir martalik. npm run lint, format:check, tip — toza.
159 ta brauzer tekshiruvi — 159/159. Lekin bu raqamga oson yetilmadi, va bu yerda muhim saboq bor. Birinchi urinishda natija 150/150 chiqdi — hammasi yashil, faqat 9 ta tekshiruv kam. Ssenariy dialog tekshiruvlarini faqat render.ts fayli bor bo'lsa ishga tushirardi. Fayl render.tsx bo'lgach, butun blok jim o'tkazib yuborildi. Ssenariy tuzatildi: endi xususiyat yo'q bo'lsa, tekshiruv yiqiladi. Qoida: "hammasi yashil" bilan birga nechta tekshiruv o'tganiga ham qarang.
git add -A
git commitrefactor: ro'yxat qatorlarini React chizadi (props, children, key)
<ul id="royxat"> ichi — React ildizi; holat va hodisalar hali vanilla.
render() — flushSync(root.render). Birinchi komponent testlari:
Vitest 5.0.4 + Testing Library + jsdom.Diff: 17 fayl, +1250 −95 (lock-fayl bilan). PR — avvalgidek, gh pr merge --merge. Foydalanuvchi uchun hech narsa o'zgarmadi — va maqsad aynan shu edi.
8. Ko'p uchraydigan xatolar
| Belgi | Sabab | Davosi |
|---|---|---|
unique "key" prop |
map da key yo'q yoki ichki elementda |
map qaytargan eng tashqi elementga |
two children with the same key |
id takrorlangan | ma'lumot manbasini tuzating |
| matn boshqa qatorga "ko'chdi" | key={index} + o'chirish/saralash |
key={item.id} |
| har chizishda matn va fokus yo'qoladi | renderda crypto.randomUUID() |
id'ni yaratishda bering |
| ro'yxat bo'sh, xato yo'q | map da { … } va return yo'q |
(…) => qavs yoki return |
| asl massiv tartibi buzildi | renderda sort() |
toSorted() |
9. Mashqlar
Mashqlar kurs/mashqlar/17/09-royxat-key/ da. Har yechimni «Ishga tushir» bilan sinang.
1-mashq (oson): key'ni toping
Har qator uchun eng yaxshi key ni tanlang. Variantlar: (a) foydalanuvchilar ro'yxati — { id, phone, name }; (b) haftaning yetti kuni — ["Dushanba", …], o'zgarmaydi; (c) buyurtma qatorlari — { dishId, count }, bitta taom bir buyurtmada bir marta; (d) izohlar — { text, author }, id yo'q.
- (a) uchun:
- (b) uchun kun nomi yoki indeks — ikkalasi ham bo'ladimi (ha/yo'q):
Yechim
(a) id — telefon ham noyob, lekin u o'zgarishi mumkin (barqaror emas). (b) ro'yxat statik, nomlar noyob — kun nomi ham, indeks ham ishlaydi; nom aniqroq. (c) dishId — bitta buyurtmada takrorlanmaydi. (d) Ishonchli key yo'q: text takrorlanishi mumkin, indeks esa o'chirishda xato beradi. To'g'ri yo'l — izoh yaratilganda id berish (server yoki crypto.randomUUID() qo'shish paytida).
2-mashq (o'rta): menyu bo'limlari
menu massividan ikki darajali ro'yxat chizing: har bo'lim (group) — <h3>, uning ostida <ul> va taomlar. Faqat soldOut: false taomlar chiqsin. Bo'limda taom qolmasa, bo'lim umuman chiqmasin. Har map da to'g'ri key bo'lsin, konsol toza bo'lsin.
const menu = [
{ id: 1, group: "Issiq taomlar", name: "Osh", soldOut: false },
{ id: 2, group: "Issiq taomlar", name: "Somsa", soldOut: true },
{ id: 3, group: "Ichimliklar", name: "Ko'k choy", soldOut: false },
{ id: 4, group: "Shirinliklar", name: "Chak-chak", soldOut: true },
{ id: 5, group: "Issiq taomlar", name: "Manti", soldOut: false },
];Ekranda shunday bo'lsin:
Issiq taomlar
Osh
Manti
Ichimliklar
Ko'k choyIshora: avval filter, keyin bo'limlar ro'yxati: [...new Set(available.map((d) => d.group))] (Set). Har bo'lim uchun yana filter + map. Ikkita map — ikkita key: tashqisida bo'lim nomi (noyob), ichkisida taom id.
Yechim
const menu = [
{ id: 1, group: "Issiq taomlar", name: "Osh", soldOut: false },
{ id: 2, group: "Issiq taomlar", name: "Somsa", soldOut: true },
{ id: 3, group: "Ichimliklar", name: "Ko'k choy", soldOut: false },
{ id: 4, group: "Shirinliklar", name: "Chak-chak", soldOut: true },
{ id: 5, group: "Issiq taomlar", name: "Manti", soldOut: false },
];
function App() {
const available = menu.filter((dish) => !dish.soldOut);
const groups = [...new Set(available.map((d) => d.group))];
return (
<div>
{groups.map((group) => (
<section key={group}>
<h3>{group}</h3>
<ul>
{available
.filter((dish) => dish.group === group)
.map((dish) => (
<li key={dish.id}>{dish.name}</li>
))}
</ul>
</section>
))}
</div>
);
}Ekranda:
Issiq taomlar
Osh
Manti
Ichimliklar
Ko'k choy"Shirinliklar" chiqmadi: bo'limlar ro'yxati available dan yasalgan, u yerda esa chak-chak yo'q. Bo'sh bo'lim uchun alohida shart kerak bo'lmadi. key={group} — bo'lim nomlari Set dan, demak noyob. Ichki key={dish.id} faqat o'z <ul> idagi qardoshlar orasida noyob bo'lishi yetadi.
3-mashq (qiyin): index key xatosini tuzating (TypeScript)
Sardor buyurtma ro'yxatini yozdi: har qatorda taom nomi va miqdor maydoni (<input type="number">). "Saralash" tugmasi ro'yxatni nom bo'yicha tartiblaydi. Mijoz oshga 3 yozib, "Saralash" ni bosdi — 3 boshqa taomga o'tib ketdi. Kodni .tsx da tuzating. Shartlar: Order turi { id: number; dish: string }; saralash asl massivni o'zgartirmasin (toSorted, localeCompare); key to'g'ri bo'lsin.
// ❌ Sardorning kodi (qisqartirilgan)
function draw() {
root.render(
<>
<ul>
{orders.map((o, i) => (
<li key={i}>
{o.dish}:{" "}
<input type="number" aria-label={`${o.dish} soni`} />
</li>
))}
</ul>
<button onClick={sortOrders}>Saralash</button>
</>
);
}
function sortOrders() {
orders.sort((a, b) => a.dish.localeCompare(b.dish));
draw();
}Tuzatilgan versiyada orders — [{ id: 1, dish: "Osh" }, { id: 2, dish: "Manti" }, { id: 3, dish: "Lag'mon" }]. "Osh soni" ga 3 yozib, "Saralash" bosilganda konsolda har maydon nomi va qiymati chiqsin — 3 oshda qolsin.
Yechim
import { flushSync } from "react-dom";
import { createRoot } from "react-dom/client";
type Order = { id: number; dish: string };
const container = document.getElementById("root");
if (container === null) throw new Error("#root topilmadi");
const root = createRoot(container);
let orders: readonly Order[] = [
{ id: 1, dish: "Osh" },
{ id: 2, dish: "Manti" },
{ id: 3, dish: "Lag'mon" },
];
function draw() {
root.render(
<>
<ul>
{orders.map((o) => (
<li key={o.id}>
{o.dish}:{" "}
<input type="number" aria-label={`${o.dish} soni`} />
</li>
))}
</ul>
<button onClick={sortOrders}>Saralash</button>
</>
);
}
function sortOrders() {
orders = orders.toSorted((a, b) => a.dish.localeCompare(b.dish));
flushSync(draw);
for (const input of document.querySelectorAll("input")) {
console.log(input.getAttribute("aria-label"), input.value);
}
}
draw();Konsolda:
Lag'mon soni
Manti soni
Osh soni 3Ekranda:
Lag'mon:
Manti:
Osh:
SaralashIkki tuzatish. Birinchisi — key={o.id}: saralashda React qatorlarni id bo'yicha taniydi va DOM elementlarini ko'chiradi, ichidagi yozuv bilan birga. Ikkinchisi — toSorted: asl massivni buzmaydi. Hatto readonly Order[] turi sort() ni umuman taqiqlaydi: orders.sort yozilsa, tsc xato beradi.
4-mashq: Vazifalar qadami
«Vazifalar qadami» bo'limidagi hamma o'zgarishni kiriting: VazifaRoyxati.tsx, render.tsx, Vitest sozlamalari va test fayli. Keyin npm run check, npm run build va brauzerda tekshiring. Commit va PR — bo'limdagi xabar bilan.
Yechim
Tekshiruv ro'yxati:
npm test—node:test146/146 va Vitest 5/5. Vitestnode:testimportida yiqilsa —vitest.config.tsdagiincludefaqat*.test.tsxni olayotganini tekshiring.npm run tip—tsconfig.jsondagireferencesdatsconfig.test.jsonbormi? Bo'lmasa test fayllari tip tekshiruvidan chetda qoladi.- Brauzerda DevTools → Elements:
<ul id="royxat">ichidagi<li>lar React chizgani. Ularni farqlash uchun React DevTools'da komponentlar daraxtini oching (React DevTools):VazifaRoyxati→VazifaQatori→OddiyQator→Qator. - Vazifani belgilang: konsolda React ogohlantirishi yo'q bo'lsin.
checked … without an onChangechiqsa —OddiyQatordareadOnlyunutilgan. - Bir vazifani tahrirga oching, maydonga yozing, boshqa tabda boshqa vazifani o'zgartiring — yozilgan matn joyida qolishi kerak (
key+defaultValue).
Yakuniy kod — refactor: ro'yxat qatorlarini React chizadi (props, children, key) commit'i.
10. Real ishda
- Serverdan kelgan ro'yxat — har sahifada. Backend id beradi, frontend uni key qiladi. Id yo'q API — kod ko'rigida "id qo'shing" deb qaytariladi.
- Index key — kod ko'rigida eng ko'p belgilanadigan React xatolaridan biri. ESLint'ning
eslint-plugin-reactto'plamidareact/no-array-index-keyqoidasi bor. - Uzun ro'yxatlar (10 000+ qator) — barcha qatorni chizish og'ir. Ekranda ko'rinadiganlarini chizish usuli — Uzun ro'yxatlar: virtualizatsiya darsida.
- Key bilan reset —
keyni ataylab o'zgartirib, komponentni noldan yasash usuli. Bu State'ning saqlanishi va key bilan reset darsida. - Intervyu savollari: "
keynima uchun kerak?", "Nega index key yomon?", "Key faqat ro'yxatdami?", "Math.random()ni key qilsa nima bo'ladi?".
Xulosa
- Ro'yxat —
array.map(item => <X key={item.id} … />); hisob-kitob (filter,toSorted)returndan oldin. key—mapqaytargan eng tashqi elementda; qardoshlar orasida noyob, barqaror, ma'lumotdan.- Index key o'chirish, qo'shish va saralashda yozilgan matnni boshqa qatorga "ko'chiradi".
- Renderda
crypto.randomUUID()har chizishda hamma qatorni qayta yasaydi — id'ni ma'lumot yaratilganda bering. keyprops'ga kirmaydi; turi —string | number | bigint.vazifalarda ro'yxat React'da:VazifaRoyxati+flushSync, Vitest 5/5, brauzer 159/159 — va "nechta tekshiruv o'tdi" ga ham qarash kerakligi.
Keyingi dars: Pure komponentlar va render qoidasi — komponent chizish paytida nimani qila oladi va nimani qilmasligi kerak, va StrictMode buni qanday tekshiradi.
Manbalar
- React: "Rendering Lists", "Keeping list items in order with key" — react.dev/learn/rendering-lists
- React: "Preserving and Resetting State" — react.dev/learn/preserving-and-resetting-state
- Vitest: "Getting Started",
environment: 'jsdom'— vitest.dev/guide - Testing Library: "Guiding Principles",
getByRole— testing-library.com/docs
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!