Mundarija (34)
- Bu darsda
- 1. Nega bu kerak?
- 2. Birinchi ishlovchi
- 2.1 onClick
- 2.2 f va f() — eng ko'p uchraydigan xato
- 2.3 Argument uzatish
- 3. Hodisa obyekti
- 3.1 SyntheticEvent
- 3.2 onChange — har harfda
- 3.3 onSubmit va preventDefault
- 4. Tarqalish: bubble, capture va stopPropagation
- 4.1 Hodisa yuqoriga ko'tariladi
- 4.2 stopPropagation
- 4.3 Ichkarida React ham delegatsiya qiladi
- 5. Klaviatura hodisalari
- 6. Nomlash va ishlovchini uzatish
- 7. TypeScript: hodisa turlari
- 8. Vazifalar qadami: qator hodisalari React'da
- 8.1 Hodisalar shartnomasi — QatorAmallari
- 8.2 Tugma va qator
- 8.3 Tahrir qatori: Enter va Esc
- 8.4 Ota beradi: {...amallar}
- 8.5 asosiy.ts — delegatsiya o'rniga ulash
- 8.6 knip: keraksiz eksportlar
- 8.7 Testlar: vi.fn() va fireEvent
- 9. Ko'p uchraydigan xatolar
- 10. Mashqlar
- 1-mashq (oson): qachon ishlaydi?
- 2-mashq (o'rta): buyurtma tugmalari
- 3-mashq (qiyin): kartochka va klaviatura (TypeScript)
- 4-mashq: Vazifalar qadami
- 11. Real ishda
- Xulosa
- Manbalar
Hodisalarni boshqarish: React'da onClick, onChange va onSubmit — funksiyani uzating, chaqirmang
Qisqacha: React'da hodisa ishlovchisi JSX atributi bilan ulanadi:
<button onClick={handleClick}>. Unga funksiyaning o'zi beriladi —handleClick, chaqiruvihandleClick()emas: aks holda u chizish paytida bajariladi. Argument kerak bo'lsa — arrow funksiya:onClick={() => removeDish(dish.id)}. Ishlovchi brauzer hodisasining o'rami — SyntheticEvent — ni oladi:preventDefault(),stopPropagation(),targetodatdagidek ishlaydi. React'ningonChangei har harfda ishga tushadi (brauzerdagichangedan farqli). Nomlash: prop —onDelete, ishlovchi —handleDelete.
Bu darsda
onClick,onChange,onSubmit,onKeyDownishlovchilarini ulaysiz.onClick={f}vaonClick={f()}farqini tushuntira olasiz va argumentni arrow bilan uzatasiz.preventDefault,stopPropagationva capture fazasini React'da ishlatasiz.- Hodisa ishlovchisini props bilan pastga uzatasiz va to'g'ri nomlaysiz.
vazifalarqatorlaridagi delegatsiyani React hodisalariga almashtirasiz.
Oldin bilishingiz kerak: Pure komponentlar va render qoidasi, addEventListener asoslari, Hodisa tarqalishi: capture va bubble, Event delegation.
1. Nega bu kerak?
Pure komponentlar darsida qoida bor edi: render faqat hisoblaydi, ish esa boshqa joyda bajariladi. O'sha joy — hodisa ishlovchisi. Tugma bosildi — buyurtma qo'shiladi. Maydonga yozildi — qidiruv yangilanadi. Forma yuborildi — ma'lumot serverga ketadi.
Vanilla'da buni addEventListener bilan qilardik. vazifalar v6 da esa butun ro'yxatga bitta tinglovchi qo'yilgan edi (Event delegation). U har bosishda closest("[data-amal]") bilan qaysi tugma ekanini qidirib, switch bilan tanlardi. Bu ishlaydi, lekin tugma va uning vazifasi ikki xil faylda yashaydi: <button data-amal="ochir"> — render.ts da, "o'chir" ning ma'nosi — asosiy.ts dagi switch da.
React'da tugma va uning ishi bir joyda yoziladi: <button onClick={…}>. Hayotiy o'xshatish: restorandagi chaqiruv tugmasi. Vanilla delegatsiya — zaldagi bitta umumiy qo'ng'iroq: ofitsiant kelib, "qaysi stoldan?" deb so'raydi. React — har stolga o'z tugmasi: bosildi — ofitsiant aynan qayerga borishni biladi.
2. Birinchi ishlovchi
2.1 onClick
function OrderButton() {
function handleClick() {
console.log("Buyurtma qabul qilindi!");
}
return <button onClick={handleClick}>Buyurtma berish</button>;
}
function App() {
return <OrderButton />;
}Konsolda:
Buyurtma qabul qilindi!Ekranda:
Buyurtma berishUch narsa. Atribut nomi — camelCase onClick (JSX I da kichik harfli onclick ishlamasligini ko'rgandik). Qiymati — {} ichida funksiya. Funksiya komponent ichida e'lon qilingan — demak u komponentning props'ini ko'radi (closure).
Qisqa ishlovchini to'g'ridan-to'g'ri yozish ham mumkin: onClick={() => console.log("…")}. Bir-ikki qatorlik ish uchun — qulay. Uzunrog'i alohida funksiyada o'qilishi oson.
2.2 f va f() — eng ko'p uchraydigan xato
function sayHello() {
console.log("Salom!");
}
function App() {
return <button onClick={sayHello()}>Salom</button>; // ❌ () bor
}Konsolda:
Salom!Ekranda:
SalomKonsolda bitta "Salom!" bor — lekin u bosishdan emas. {sayHello()} — funksiya chizish paytida chaqirildi, uning natijasi (undefined) esa onClick ga berildi. Blok tugmani bosdi, lekin hech narsa bo'lmadi: ishlovchi yo'q. StrictMode'da "Salom!" chizishning o'zida ikki marta chiqardi.
| Yozuv | Nima uzatiladi | Qachon ishlaydi |
|---|---|---|
onClick={sayHello} |
funksiyaning o'zi | bosilganda |
onClick={() => sayHello()} |
yangi funksiya | bosilganda |
onClick={sayHello()} |
chaqiruv natijasi | chizish paytida |
Bu xato ba'zan jim emas. Ishlovchi state'ni o'zgartirsa (keyingi dars), chizish → o'zgartirish → yana chizish zanjiri cheksiz aylanadi va React to'xtatadi. TypeScript esa uni darhol ushlaydi:
function sayHello() {
console.log("Salom!");
}
export function App() {
return <button onClick={sayHello()}>Salom</button>;
}Xato:
TS2322: Type 'void' is not assignable to type 'MouseEventHandler<HTMLButtonElement> | undefined'."void turini MouseEventHandler<HTMLButtonElement> ga berib bo'lmaydi" — ya'ni onClick funksiya kutyapti, siz esa "hech narsa" (sayHello() natijasi) berdingiz.
2.3 Argument uzatish
Ro'yxatda har tugma o'z taomini o'chirishi kerak. Ishlovchiga id kerak — lekin onClick={removeDish(dish.id)} — o'sha f() xatosi. Yechim — arrow funksiya bilan "o'rash":
function DishList({ dishes, onRemove }) {
return (
<ul>
{dishes.map((dish) => (
<li key={dish.id}>
{dish.name}{" "}
<button
aria-label={`O'chirish: ${dish.name}`}
onClick={() => onRemove(dish.id)}
>
×
</button>
</li>
))}
</ul>
);
}
function App() {
const dishes = [
{ id: 1, name: "Osh" },
{ id: 2, name: "Manti" },
];
return (
<DishList
dishes={dishes}
onRemove={(id) => console.log("O'chirilsin:", id)}
/>
);
}Konsolda:
O'chirilsin: 2Ekranda:
Osh ×
Manti ×() => onRemove(dish.id) — har qator uchun yangi funksiya. U chizishda yaratiladi, lekin chaqirilmaydi. Bosilganda esa o'zi eslab qolgan dish.id bilan onRemove ni chaqiradi (closure). Blok "Manti" tugmasini aria-label orqali topdi — Malika ham ekran o'quvchida aynan shuni eshitadi.
DishList o'chirishni o'zi qilmaydi — onRemove prop'ini chaqiradi. "Nima bo'ldi" ni bola aytadi, "nima qilish" ni ota hal qiladi. Bu Props darsidagi "funksiya ham prop" ning eng muhim ishlatilishi. Hozircha ro'yxat o'zgarmaydi — o'chirish uchun ma'lumot o'zgarishi kerak, buni useState bilan qilamiz.
Tekshirib ko'ring:
<button onClick={console.log("bosildi")}>— nima bo'ladi?
Javob
console.log("bosildi") chizish paytida bir marta (StrictMode'da ikki marta) ishlaydi va undefined qaytaradi — onClick={undefined}. Bosilganda hech narsa bo'lmaydi. To'g'risi: onClick={() => console.log("bosildi")}.
3. Hodisa obyekti
3.1 SyntheticEvent
Ishlovchi birinchi argument sifatida hodisa obyektini oladi — vanilla'dagidek (Event obyekti). Lekin bu brauzerning asl hodisasi emas, React'ning o'rami — SyntheticEvent ("sun'iy hodisa"). Uning maydonlari brauzerdagidek nomlangan, hamma brauzerda bir xil ishlaydi. Asl hodisa kerak bo'lsa — event.nativeEvent:
function App() {
function handleClick(event) {
console.log(event.type, event.target.tagName);
console.log("asli:", event.nativeEvent instanceof MouseEvent);
}
return <button onClick={handleClick}>Savatga</button>;
}Konsolda:
click BUTTON
asli: trueEkranda:
SavatgaEski kodda event.persist() ni uchratasiz — React 16 gacha hodisa obyekti qayta ishlatilardi. React 17 dan beri bu kerak emas, persist() hech narsa qilmaydi.
3.2 onChange — har harfda
Bu yerda React vanilla'dan farq qiladi. Brauzerdagi change hodisasi maydon fokusni yo'qotganda ishlaydi, input — har harfda (input va change). React'ning onChange i esa har harfda ishlaydi — aslida u brauzerning input hodisasiga ulangan:
function App() {
function handleChange(event) {
console.log(event.target.value, "|", event.nativeEvent.type);
}
return <input aria-label="Taom" onChange={handleChange} />;
}Konsolda:
Osh | inputEkranda:
(bo'sh)nativeEvent.type — "input". Blok matnni bir harakatda yozdi, shuning uchun bitta qator. Klaviaturada yozsangiz — har harf uchun bittadan. React jamoasi bu nomni tarixan tanlagan: "maydon o'zgardi" — har o'zgarishda. Vanilla'dan kelganlar uchun bu kutilmagan, shuning uchun eslab qoling: React onChange = brauzer input.
3.3 onSubmit va preventDefault
Forma yuborilganda brauzer sahifani qayta yuklaydi. SPA'da bunga yo'l qo'yilmaydi — vanilla'dagidek preventDefault() (standart harakatlar):
function App() {
function handleSubmit(event) {
event.preventDefault(); // sahifa qayta yuklanmasin
const data = new FormData(event.currentTarget);
console.log("Yangi taom:", data.get("dish"));
}
return (
<form onSubmit={handleSubmit}>
<input name="dish" aria-label="Taom" />
<button type="submit">Qo'shish</button>
</form>
);
}Konsolda:
Yangi taom: OshEkranda:
Qo'shishonSubmit — <form> da, tugmada emas. Shunda Enter bosilganda ham ishlaydi (Bobur faqat klaviatura bilan ishlaydi). event.currentTarget — ishlovchi ulangan element, ya'ni forma. FormData uni bir qatorda o'qiydi. React'dagi formalar mavzusi katta — Formalar I va Formalar II darslarida. U yerda React 19 ning <form action> imkoniyati ham bor.
Diqqat: React'da
return falsestandart harakatni to'xtatmaydi (eski jQuery vaonclick="return false"odati). Faqatevent.preventDefault().
4. Tarqalish: bubble, capture va stopPropagation
4.1 Hodisa yuqoriga ko'tariladi
React hodisalari ham bubble qiladi: ichki elementdagi bosish otalarga ham yetadi. Capture fazasi uchun — nom oxirida Capture:
function App() {
return (
<div
onClickCapture={() => console.log("1. div — capture")}
onClick={() => console.log("3. div — bubble")}
>
<button onClick={() => console.log("2. button")}>
Savatga
</button>
</div>
);
}Konsolda:
1. div — capture
2. button
3. div — bubbleEkranda:
SavatgaTartib brauzerdagidek: avval tepadan pastga (capture), keyin nishon, keyin pastdan tepaga (bubble).
| Faza | Vanilla | React |
|---|---|---|
| capture | addEventListener("click", f, true) |
onClickCapture={f} |
| nishon va bubble | addEventListener("click", f) |
onClick={f} |
| to'xtatish | event.stopPropagation() |
event.stopPropagation() |
4.2 stopPropagation
Taom kartasi butunlay bosiladigan: bosilsa, tafsilotlar ochiladi. Ichida "Savatga" tugmasi bor. Tugma bosilganda karta ochilmasligi kerak:
function DishCard() {
return (
<div onClick={() => console.log("Karta ochildi")}>
<h3>Osh</h3>
<button
onClick={(event) => {
event.stopPropagation();
console.log("Savatga qo'shildi");
}}
>
Savatga
</button>
</div>
);
}
function App() {
return <DishCard />;
}Konsolda:
Savatga qo'shildiEkranda:
Osh
Savatga"Karta ochildi" chiqmadi — hodisa tugmada to'xtadi. stopPropagation ni ehtiyotkorlik bilan ishlating: yuqoridagi boshqa tinglovchilar (masalan, analitika yoki "tashqarida bosilsa menyuni yop") ham hodisani ko'rmay qoladi.
4.3 Ichkarida React ham delegatsiya qiladi
Qiziq fakt: React har onClick uchun alohida addEventListener qo'ymaydi. U ilova ildiziga (createRoot olgan elementga) har hodisa turidan bittadan tinglovchi qo'yadi. Hodisa kelganda, qaysi komponentning ishlovchisini chaqirishni o'zi topadi.
flowchart LR
B["button: bosildi"] -- "bubble" --> U["ul"]
U -- "bubble" --> R["#root: React tinglovchisi"]
R --> F["komponentlar daraxtidan yo'l"]
F --> H["onClick'lar chaqiriladi: ichkaridan tashqariga"]Brauzer hodisasi DOM bo'ylab ildizgacha ko'tariladi. U yerda React uni ushlaydi, o'z komponentlar daraxtidan bosilgan elementgacha yo'lni topadi va yo'ldagi onClick larni tartib bilan chaqiradi. Ya'ni v6 dagi delegatsiya yo'qolmadi — u React'ning ichiga ko'chdi. Siz uchun esa har tugma o'z ishlovchisi bilan yoziladi.
Buning bitta amaliy oqibati bor. dispatchEvent bilan sun'iy hodisa yuborayotgan kod (masalan, testlar) bubbles: true ni unutsa — hodisa ildizga yetib bormaydi. Unda React uni ko'rmaydi. vazifalar ning brauzer tekshiruvida aynan shu tuzoq uchraydi — State III darsida.
5. Klaviatura hodisalari
onKeyDown — vanilla keydown (Klaviatura hodisalari). event.key — bosilgan tugma nomi:
function App() {
function handleKeyDown(event) {
if (event.key === "Escape") {
event.currentTarget.value = "";
console.log("Tozalandi");
}
}
return (
<>
<input aria-label="Qidiruv" onKeyDown={handleKeyDown} />
<button
id="tozala"
onClick={() => {
const input = document.querySelector("input");
const esc = new KeyboardEvent("keydown", {
key: "Escape",
bubbles: true,
});
input.dispatchEvent(esc);
}}
>
Esc'ni taqlid qilish
</button>
</>
);
}Konsolda:
TozalandiEkranda:
Esc'ni taqlid qilishNatija oynasida maydonga yozib, Esc'ni bosing — matn o'chadi. Tugma Esc bosilishini dispatchEvent bilan taqlid qiladi (tekshiruv shu yo'l bilan ishlaydi). Unda bubbles: true borligiga e'tibor bering — «Ichkarida React ham delegatsiya qiladi» bo'limidagi qoida.
Bitta nozik holat — IME (xitoy, yapon, koreys yozuvi kiritish usuli). Bunday klaviaturada so'z tanlash oynasidagi Enter "saqla" degani emas — u faqat so'zni tanlaydi. Brauzer buni event.isComposing bilan bildiradi. React'da u event.nativeEvent.isComposing da. vazifalar dagi tahrir maydoni aynan shunday tekshiradi — «Vazifalar qadami» bo'limida.
6. Nomlash va ishlovchini uzatish
React'da ikki qoida keng tarqalgan:
| Nima | Qoida | Misol |
|---|---|---|
| hodisa prop'i (komponentning "kirishi") | on + Hodisa |
onRemove, onOrder, onSave |
| ishlovchi funksiya (ichki) | handle + Hodisa |
handleRemove, handleSubmit |
Ota bolaga onRemove={handleRemove} deb beradi: "o'chirish hodisasi bo'lganda — mening handleRemove imni chaqir". vazifalar da nomlar o'zbekcha: prop — onAlmashtir, onOchir. Ishlovchi esa kursdagi funksiya qoidasi bilan — fe'l: katakniAlmashtir, ochirishniSorab.
O'z komponentingizda hodisa prop'ini xohlagancha nomlash mumkin. <button> kabi brauzer elementlarida esa faqat React biladigan nomlar ishlaydi: onClick, onChange, onKeyDown, onSubmit, onFocus, onPointerDown va boshqalar. <button onTap={…}> — xato emas, lekin hech narsa qilmaydi. React konsolda ogohlantiradi: Unknown event handler property `onTap`. It will be ignored. — "noma'lum hodisa xususiyati, e'tiborsiz qoldiriladi".
7. TypeScript: hodisa turlari
Ishlovchi alohida funksiya bo'lsa, unga tur kerak. React o'z hodisa turlarini react paketidan beradi:
import type { ChangeEvent, FormEvent, KeyboardEvent } from "react";
export default function App() {
function handleChange(event: ChangeEvent<HTMLInputElement>) {
console.log("yozildi:", event.target.value);
}
function handleKeyDown(event: KeyboardEvent<HTMLInputElement>) {
if (event.key === "Enter" && !event.nativeEvent.isComposing) {
console.log("Enter");
}
}
function handleSubmit(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
}
return (
<form onSubmit={handleSubmit}>
<input
aria-label="Taom"
onChange={handleChange}
onKeyDown={handleKeyDown}
/>
</form>
);
}Ekranda:
(bo'sh)<HTMLInputElement> — hodisa qaysi elementda ekani: shuning uchun event.target.value turi string va xatosiz o'qiladi. Eng ko'p uchraydigan xato — turni import qilishni unutish. Unda brauzerning global KeyboardEvent turi olinadi:
function handleKey(event: KeyboardEvent) {
console.log(event.key);
}
export function App() {
return <input onKeyDown={handleKey} />;
}Xato:
TS2322: Type '(event: KeyboardEvent) => void' is not assignable to type 'KeyboardEventHandler<HTMLInputElement>'.
Types of parameters 'event' and 'event' are incompatible.
Type 'KeyboardEvent<HTMLInputElement>' is missing the following properties from type 'KeyboardEvent': isComposing, initKeyboardEvent, DOM_KEY_LOCATION_STANDARD, DOM_KEY_LOCATION_LEFT, and 14 more."React'ning KeyboardEvent<HTMLInputElement> turida brauzer KeyboardEvent idagi isComposing va yana 17 ta xususiyat yo'q." Ikki xil tur bir xil nomli — biri React'niki, biri brauzerniki. Tuzatish: import type { KeyboardEvent } from "react". Xabarning o'zi isComposing nega event.nativeEvent da ekanini ham ko'rsatdi: React o'ramida u yo'q. Hodisa turlarining to'liq ro'yxati — React + TS III: hodisalar darsida.
8. Vazifalar qadami: qator hodisalari React'da
Ro'yxatni React chizyapti (Ro'yxatlar va key), lekin bosishni hali vanilla delegatsiya ushlaydi. Bugun uni olib tashlaymiz. Kanon fayllaridagi uzun izoh qatorlari telefon uchun qayta bo'lingan. Yangi branch:
git switch -c refactor/qator-hodisalari8.1 Hodisalar shartnomasi — QatorAmallari
Avval qatorning "kirishlari" — qaysi hodisalar bo'lishi. src/turlar.ts oxiriga:
/**
* Ro'yxat qatoridagi hodisalar (17/#11). Ota komponent beradi, qator
* chaqiradi: qator "nima bo'ldi"ni aytadi, "nima qilish"ni ota hal
* qiladi. Nomlash: prop — onX, ishlovchi — fe'l
* (katakniAlmashtir)
*/
export type QatorAmallari = {
readonly onAlmashtir: (id: number) => void;
readonly onOchir: (id: number) => void;
readonly onTahrirla: (id: number) => void;
readonly onSaqla: () => void;
readonly onBekor: () => void;
};Beshta hodisa — v6 dagi switch ning beshta case i. Izohdagi qoida — «Nomlash va ishlovchini uzatish» bo'limidagi jadvalning o'zi.
8.2 Tugma va qator
AmalTugmasi ga onClick prop'i qo'shiladi:
type Props = {
amal: Amal;
matn: string; // tugmada ko'rinadigan so'z
nomi: string; // vazifa matni
+ onClick: () => void;
};
-export function AmalTugmasi({ amal, matn, nomi }: Props) {
+export function AmalTugmasi({ amal, matn, nomi, onClick }: Props) {
return (
- // data-amal — vanilla hodisa tinglovchisi shunga qaraydi
- // (#11 gacha)
+ // onClick={onClick} — funksiyaning O'ZI uzatiladi;
+ // onClick={onClick()} bo'lsa, u render paytida chaqirilardi
+ // (17/#11). data-amal — e2e ssenariy va fokus shunga qaraydi
+ // (render.tsx)
<button
type="button"
data-amal={amal}
aria-label={`${matn}: ${nomi}`}
+ onClick={onClick}
>OddiyQator — katakda readOnly o'rniga onChange, tugmalarda arrow bilan id (Shartli render darsida va'da qilingan almashinuv):
<input
type="checkbox"
id={katakId}
data-amal="almashtir"
checked={vazifa.bajarildi}
- readOnly
+ onChange={() => onAlmashtir(vazifa.id)}
/>
…
<AmalTugmasi
amal="tahrirla"
matn="Tahrirlash"
nomi={vazifa.matn}
+ onClick={() => onTahrirla(vazifa.id)}
+ />Props turi esa QatorAmallari dan faqat keraklisini oladi: Pick<QatorAmallari, "onAlmashtir" | "onOchir" | "onTahrirla"> (Utility types).
Katakdagi o'zgarish muhim. checked holatdan keladi — bu controlled katak. Bosilganda React avval katakni o'z joyiga qaytaradi va faqat holat o'zgargach yangisini chizadi. Buni jonli ko'ring:
function App() {
return (
<label>
<input
type="checkbox"
checked={false}
onChange={(event) => {
console.log("onChange ichida:", event.target.checked);
setTimeout(() => {
console.log("keyin:", event.target.checked);
}, 0);
}}
/>
Non olish
</label>
);
}Konsolda:
onChange ichida: true
keyin: falseEkranda:
Non olishBrauzer katakni belgiladi (true), React esa holatda false turganini ko'rib, uni qaytardi. Bu vazifalar uchun foyda. Server rad etsa yoki internet bo'lmasa, holat o'zgarmaydi va katak o'zi eski holatida qoladi. v6 da buning uchun fondaChiz bilan qo'lda qaytarish kerak edi.
8.3 Tahrir qatori: Enter va Esc
TahrirQatori dagi klaviatura — v6 dagi royxat.addEventListener("keydown", …) ning o'rnida:
export function TahrirQatori({ vazifa, onSaqla, onBekor }: Props) {
// Enter — saqlash, Esc — bekor. React hodisasi — SyntheticEvent:
// brauzer hodisasining o'rami, asli — event.nativeEvent. IME
// (xitoy, yapon yozuvi) tanlov oynasidagi Enter — saqlash emas
// (isComposing)
function tugmaBosildi(event: KeyboardEvent<HTMLInputElement>) {
if (event.nativeEvent.isComposing) {
return;
}
if (event.key === "Enter") {
event.preventDefault();
onSaqla();
} else if (event.key === "Escape") {
onBekor();
}
}va maydonda — onKeyDown={tugmaBosildi}, tugmalarda — onClick={onSaqla}, onClick={onBekor}. Bu yerda arrow kerak emas: onSaqla argumentsiz, funksiyaning o'zi uzatiladi. KeyboardEvent — «TypeScript: hodisa turlari» bo'limidagidek, react dan import qilingan.
8.4 Ota beradi: {...amallar}
VazifaRoyxati va VazifaQatori beshta funksiyani bitta amallar obyektida tashiydi. VazifaQatori ularni spread bilan qatorga yoyadi (Props dagi spread):
// {...amallar} — obyektdagi har bir funksiya alohida prop bo'lib
// o'tadi; qator faqat o'ziga keraklisini oladi (17/#06: spread)
if (tahrirda) {
return <TahrirQatori vazifa={vazifa} {...amallar} />;
}
return <OddiyQator vazifa={vazifa} sorov={sorov} {...amallar} />;Bu yerda spread xavfsiz: amallar DOM elementiga emas, komponentga tushadi. OddiyQator faqat o'zining uchtasini destructuring bilan oladi, onSaqla va onBekor e'tiborsiz qoladi.
8.5 asosiy.ts — delegatsiya o'rniga ulash
Eng katta o'zgarish — v6 dagi ikki tinglovchi (52 qator) o'chadi:
-royxat.addEventListener("click", (event) => {
- …
- const nishon = event.target.closest<HTMLElement>("[data-amal]");
- …
- switch (amal) {
- case "almashtir":
- katakniAlmashtir(id, zaxira);
- break;
- …
- }
-});
-
-royxat.addEventListener("keydown", (event) => { … });
+// Ro'yxat qatorlari — React komponentlari (17/#11): hodisani qator
+// o'zi ushlaydi va shu funksiyalarni chaqiradi. Avval bitta <ul> da
+// click va keydown tinglovchisi data-amal ga qarab tanlardi
+// (delegatsiya)
+qatorAmallariniUla({
+ onAlmashtir(id) {
+ elonQil("");
+ holat.tahrirId = null;
+ katakniAlmashtir(id, qoshniId(id));
+ },
+ …
+ onSaqla: tahrirniSaqla,
+ onBekor: tahrirniBekorQil,
+});qatorAmallariniUla — render.tsx dagi yangi funksiya: u obyektni saqlab qo'yadi va render() uni <VazifaRoyxati amallar={…}> ga beradi. Holat hali vanilla do'konda — shuning uchun ishlovchilar asosiy.ts da qoldi. State'ni ko'tarish darsida ular App komponentiga ko'chadi.
8.6 knip: keraksiz eksportlar
Delegatsiya olib tashlangach, uchta eksportni endi hech kim ishlatmaydi. npm run knip buni topdi (knip 6.40.0):
Unused exports (2)
TAHRIR_MAYDONI_ID src/render.tsx:8:10
royxat src/render.tsx:47:14
Unused exported types (1)
Amal type src/AmalTugmasi.tsx:4:13"Ishlatilmaydigan eksportlar." royxat (<ul>) va TAHRIR_MAYDONI_ID ni vanilla tinglovchi ishlatardi, Amal turini — render.ts. Uchalasidan export olib tashlandi. knip darsida o'rnatgan vosita birinchi marta React kodida ish berdi. Tuzatilgandan keyin npm run knip jim tugaydi.
8.7 Testlar: vi.fn() va fireEvent
Test fayliga uchta yangi test qo'shildi. Ulardagi yangi vosita — vi.fn(), "josus" funksiya: necha marta va qaysi argument bilan chaqirilganini eslab qoladi (Vitest II: mock va spy darsida chuqur). fireEvent.click — elementda hodisa chaqiradi:
test("hodisalar: katak, tahrirlash va o'chirish — vazifa id bilan",
() => {
const amallar = amallarYasa(); // beshta vi.fn()
chiz("", null, amallar);
fireEvent.click(
screen.getByRole("checkbox", { name: "Non olish #bozor" }),
);
…
expect(amallar.onAlmashtir).toHaveBeenCalledWith(1);
expect(amallar.onTahrirla).toHaveBeenCalledWith(2);
expect(amallar.onOchir).toHaveBeenCalledWith(1);
});Qolgan ikkitasi: "controlled katak: bosilsa ham holat o'zgarmaguncha eski qiymat" (yuqoridagi jonli misolning testi) va "Enter — saqlash, Esc — bekor, IME tanlovidagi Enter — yo'q". npx vitest run --reporter=verbose (oxirgi uch qator):
✓ tekshiruv/VazifaRoyxati.test.tsx > VazifaRoyxati > hodisalar: katak, tahrirlash va o'chirish — vazifa id bilan 22ms
✓ tekshiruv/VazifaRoyxati.test.tsx > VazifaRoyxati > controlled katak: bosilsa ham holat o'zgarmaguncha eski qiymat 8ms
✓ tekshiruv/VazifaRoyxati.test.tsx > VazifaRoyxati > tahrir: Enter — saqlash, Esc — bekor, IME tanlovidagi Enter — yo'q 8ms
Tests 8 passed (8)node:test — 146/146, npm run lint, format:check, tip, knip — toza. Brauzer tekshiruvi — 159/159, birinchi urinishda. Ssenariy React hodisalari haqida hech narsa bilmaydi: u haqiqiy bosish qiladi, React esa uni ildizda ushlaydi.
refactor: qator hodisalari React'da — delegatsiya o'rniga onClickDiff: 9 fayl, +208 −86.
9. Ko'p uchraydigan xatolar
| Belgi | Sabab | Davosi |
|---|---|---|
| ishlovchi chizishda ishlaydi, bosishda yo'q | onClick={f()} |
onClick={f} yoki () => f(x) |
TS2322 … 'void' is not assignable to … EventHandler |
o'sha | o'sha |
onclick ishlamaydi |
kichik harf | onClick |
| sahifa qayta yuklanadi | preventDefault() yo'q yoki return false |
event.preventDefault() |
| ichki tugma tashqi kartani ham ochadi | bubble | event.stopPropagation() |
onChange har harfda chaqiriladi |
React'da bu input hodisasi |
kutilgan xulq |
KeyboardEvent … missing … isComposing |
brauzer turi ishlatilgan | import type { KeyboardEvent } from "react" |
| sun'iy hodisani React ko'rmaydi | dispatchEvent da bubbles yo'q |
{ bubbles: true } |
10. Mashqlar
Mashqlar kurs/mashqlar/17/11-hodisalar/ da. Har yechimni «Ishga tushir» bilan sinang.
1-mashq (oson): qachon ishlaydi?
Blok tugmalarni ketma-ket bosadi: "A", "B", "C", "D". Ishga tushirmasdan oldin konsolni yozing.
function log(text) {
console.log(String(text));
}
function App() {
return (
<div onClick={() => log("div")}>
<button onClick={() => log("A")}>A</button>
<button onClick={log("B")}>B</button>
<button onClick={log}>C</button>
<button
onClick={(event) => {
event.stopPropagation();
log("D");
}}
>
D
</button>
</div>
);
}Konsolda:
B
A
div
div
[object Object]
div
DKonsolning birinchi qatori — chizish paytida chiqqan:
Yechim
- B — birinchi qator:
log("B")chizishda chaqirildi,onClickgaundefinedtushdi. Bosilganda tugmada ishlovchi yo'q, faqat div'ningonClicki ishlaydi (2-"div"). - A —
A, keyin bubble:div. - C —
onClick={log}: funksiyaning o'zi uzatildi, React uni hodisa obyekti bilan chaqirdi —log(event). Konsolda obyekt satrga aylandi:[object Object]. Keyindiv. Ishlovchi har doim hodisani birinchi argument qilib oladi — buni unutmang. - D —
stopPropagation: faqatD,divyo'q.
2-mashq (o'rta): buyurtma tugmalari
«Bahor» buyurtma ro'yxati: har qatorda taom, soni va uchta tugma: "+", "−", "×" (o'chirish). Hozircha ma'lumot vanilla o'zgaruvchida, chizish — draw() (JSX II dagidek). Yozing:
OrderRowkomponenti — props:item,onAdd,onRemoveOne,onDelete; tugmalarningaria-labeli:"Qo'sh: Osh","Ayir: Osh","O'chir: Osh";- "−" soni 1 bo'lsa
disabled; - ishlovchilar
Appda (vanilladraw()bilan), argument —item.id.
Blok ketma-ket bosadi: oshni ko'paytirish, mantini o'chirish, oshni kamaytirish. Natija:
Osh × 1 +−×
Ko'k choy × 2 +−×Yechim
import { createRoot } from "react-dom/client";
const root = createRoot(document.getElementById("root"));
let order = [
{ id: 1, dish: "Osh", count: 1 },
{ id: 2, dish: "Manti", count: 3 },
{ id: 3, dish: "Ko'k choy", count: 2 },
];
function OrderRow({ item, onAdd, onRemoveOne, onDelete }) {
return (
<li>
{item.dish} × {item.count}{" "}
<button
aria-label={`Qo'sh: ${item.dish}`}
onClick={() => onAdd(item.id)}
>
+
</button>
<button
aria-label={`Ayir: ${item.dish}`}
disabled={item.count === 1}
onClick={() => onRemoveOne(item.id)}
>
−
</button>
<button
aria-label={`O'chir: ${item.dish}`}
onClick={() => onDelete(item.id)}
>
×
</button>
</li>
);
}
function change(id, delta) {
order = order.map((item) =>
item.id === id ? { ...item, count: item.count + delta } : item
);
draw();
}
function handleDelete(id) {
order = order.filter((item) => item.id !== id);
draw();
}
function draw() {
root.render(
<ul>
{order.map((item) => (
<OrderRow
key={item.id}
item={item}
onAdd={(id) => change(id, 1)}
onRemoveOne={(id) => change(id, -1)}
onDelete={handleDelete}
/>
))}
</ul>
);
}
draw();Ekranda:
Osh × 1 +−×
Ko'k choy × 2 +−×onDelete={handleDelete} — arrow kerak emas: OrderRow uni o'zi id bilan chaqiradi. onAdd va onRemoveOne esa bitta change funksiyasini turli delta bilan o'raydi. order.map(… { ...item, count }) — massivni o'zgartirmasdan yangisini yasash. Bu State III darsining asosiy usuli, hozir u yerdan bir parcha.
3-mashq (qiyin): kartochka va klaviatura (TypeScript)
.tsx da DishCard yozing:
- butun karta (
<article>) bosilsa —onOpen()chaqirilsin; - ichidagi "Savatga" tugmasi — faqat
onAddToCart(), karta ochilmasin; - karta klaviaturadan ham ochilsin:
<article tabIndex={0}>vaonKeyDownda Enter →onOpen(). Lekin fokus ichidagi tugmada bo'lsa, Enter faqat tugmani bossin — kartani ochmasin. Ishora:event.target === event.currentTarget— hodisa kartaning o'zidami? - turlar:
MouseEvent,KeyboardEvent—reactdan.
App ishlovchilarga console.log beradi. Blok: kartani bosadi, keyin "Savatga" ni bosadi.
Yechim
import type { KeyboardEvent, MouseEvent } from "react";
type DishCardProps = {
name: string;
price: number;
onOpen: () => void;
onAddToCart: () => void;
};
function DishCard(props: DishCardProps) {
const { name, price, onOpen, onAddToCart } = props;
function handleKeyDown(event: KeyboardEvent<HTMLElement>) {
const onCard = event.target === event.currentTarget;
if (event.key === "Enter" && onCard) {
onOpen();
}
}
function handleAdd(event: MouseEvent<HTMLButtonElement>) {
event.stopPropagation();
onAddToCart();
}
return (
<article tabIndex={0} onClick={onOpen} onKeyDown={handleKeyDown}>
<h3>{name}</h3>
<p>{price} so'm</p>
<button type="button" onClick={handleAdd}>
Savatga
</button>
</article>
);
}
export default function App() {
return (
<DishCard
name="Osh"
price={35000}
onOpen={() => console.log("Karta ochildi")}
onAddToCart={() => console.log("Savatga qo'shildi")}
/>
);
}Konsolda:
Karta ochildi
Savatga qo'shildiEkranda:
Osh
35000 so'm
SavatgaIkkinchi bosishda "Karta ochildi" yo'q — stopPropagation. Klaviatura uchun event.target === event.currentTarget tekshiruvi kerak: tugmada bosilgan Enter ham keydown sifatida kartaga ko'tariladi. Shart bo'lmasa, Bobur tugmada Enter bossa, karta ham ochilardi. onClick={onOpen} — arrow emas: onOpen hodisa obyektini oladi, lekin argument kutmagani uchun uni e'tiborsiz qoldiradi. Turi () => void — TypeScript bunga ruxsat beradi.
4-mashq: Vazifalar qadami
refactor/qator-hodisalari branch'ida «Vazifalar qadami» bo'limidagi o'zgarishlarni kiriting: QatorAmallari, AmalTugmasi, OddiyQator, TahrirQatori, VazifaQatori, VazifaRoyxati, render.tsx (qatorAmallariniUla), asosiy.ts va uchta yangi test. npm run check va npm run knip toza bo'lsin.
Yechim
Tekshiruv ro'yxati:
npm test— Vitest 8/8. "controlled katak" testi yiqilsa —checkedo'rnigadefaultCheckedyozilgan bo'lishi mumkin.npm run knip— uchta keraksiz eksport (royxat,TAHRIR_MAYDONI_ID,Amal) tozalanganmi?asosiy.tsdaroyxat.addEventListenerqolmaganmi? Qolsa — har bosishda ish ikki marta bajariladi: React ham, eski tinglovchi ham.- Brauzerda: katakni belgilang, tahrirlang, Enter va Esc bilan sinang, o'chiring. Internetni o'chirib katakni bosing — katak o'zi qaytishi kerak (controlled).
Yakuniy kod — refactor: qator hodisalari React'da — delegatsiya o'rniga onClick commit'i.
11. Real ishda
- Har interfeys — hodisalar to'plami. Kod ko'rigida ko'p uchraydigan izohlar: "
onClick={handle()}— qavslarni olib tashlang", "ichki tugmadastopPropagationyo'q". - Analitika (Google Analytics, Yandex Metrika) ko'pincha hujjatda yoki ildizda bubble bosqichida tinglaydi. Keraksiz
stopPropagationularni "ko'r" qiladi — shuning uchun undan faqat haqiqatan kerak joyda foydalaning. - Testlar hodisalarni
fireEventyokiuser-eventbilan chaqiradi (RTL II). Haqiqiy foydalanuvchiga yaqinroq —user-event. - Intervyu savollari: "
onClick={f}vaonClick={f()}farqi?", "SyntheticEvent nima?", "React'daonChangeqachon ishlaydi?", "React hodisalarni qayerga ulaydi?".
Xulosa
- Ishlovchi — JSX atributi:
onClick={handleClick}; funksiyaning o'zi uzatiladi, chaqiruvi emas. - Argument kerak bo'lsa — arrow:
onClick={() => onRemove(dish.id)}. - Ishlovchi SyntheticEvent oladi:
preventDefault,stopPropagation,target,currentTarget; asli —nativeEvent. - React
onChange— har harfda (brauzerninginputhodisasi);onSubmit— formada,preventDefaultbilan. - Bubble va capture brauzerdagidek; React hodisalarni ildizda ushlaydi — sun'iy hodisaga
bubbles: truekerak. vazifalarda delegatsiya o'rnigaonClick/onChange/onKeyDown:QatorAmallari, controlled katak, Vitest 8/8, 159/159.
Keyingi dars: State I: useState — komponent xotirasi — ishlovchi ma'lumotni o'zgartirganda ekran o'zi yangilanishi uchun komponentga xotira beramiz.
Manbalar
- React: "Responding to Events" — react.dev/learn/responding-to-events
- React: "Common components" — React event object,
onChange— react.dev/reference/react-dom/components/common - React 17 blogi: "Changes to Event Delegation" (2020-08-10) — legacy.reactjs.org/blog/2020/08/10/react-v17-rc.html
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!