IlmHamroh
JavaScript Full-stack/18-qism. React chuqur36/42-dars18 daqiqa
Mundarija (27)

React + TS III: hodisalar va HTML atributlari — ChangeEvent, ComponentProps va Omit

Qisqacha: Ishlovchi JSX ichida yozilsa (onChange={(e) => …}), TypeScript hodisa turini o'zi biladi. Alohida funksiyada esa turni react dan import qilasiz: ChangeEvent<HTMLInputElement>, MouseEvent<HTMLButtonElement>, KeyboardEvent<…>. O'z tugmangiz yoki maydoningiz oddiy <button> ning hamma atributlarini (disabled, aria-label, type) qabul qilsin desangiz — props turini ComponentProps<"button"> dan oling va ...rest bilan elementga yoying. Biror atributni o'zgacha qilmoqchi bo'lsangiz — avval Omit bilan olib tashlab, keyin o'zingiznikini qo'shing.

Bu darsda

  • Hodisa ishlovchilariga tur yozasiz va qachon tur kerak emasligini (kontekstli tur) ajratasiz.
  • target va currentTarget farqini tur darajasida tushuntira olasiz.
  • ComponentProps<"button">, ComponentPropsWithoutRef va HTMLAttributes farqini bilasiz.
  • Omit bilan native prop'ni o'z turingizga almashtirasiz: onChange(value: string).
  • vazifalar dagi Tugma qanday qilib <button> ning hamma atributini olishini o'qiysiz.

Oldin bilishingiz kerak: React + TS II, Hodisalarni boshqarish, Utility types, Komponent API'sini loyihalash.

1. Nega bu kerak?

Oldingi darsda komponent ichidagi xotiraga tur berdik. Endi komponent tashqi dunyo bilan uchrashadigan ikki joyga qaraymiz.

Birinchisi — hodisa. Sardor forma ishlovchisini alohida funksiyaga chiqardi:

tsx
function handleChange(event) {
  // ❌ TS7006: Parameter 'event' implicitly has an 'any' type.
  setDish(event.target.value);
}

Ikkinchisi — o'ralgan element. Jasur aka «Bahor» uchun SaveButton yasadi, faqat label va onClick bilan. Bir hafta o'tib kerak bo'ldi: tugmani o'chirish (disabled), ekran o'quvchi uchun nom (aria-label), formada type="submit". Har biri uchun yangi prop qo'shish kerakmi? <button> da 200 dan ortiq atribut bor.

Hayotiy o'xshatish: elektr vilkasi. Uyingizdagi rozetka standart — har qanday vilka tushadi. Siz o'z uzaytirgichingizni yasasangiz, unga ham har qanday vilka tushishi kerak, faqat sizning chiroqingiz emas. ComponentProps<"button"> — o'z komponentingizni "standart rozetka" qilish.

2. Hodisa turlari

2.1 Inline ishlovchi — tur kerak emas

Ishlovchi JSX ichida yozilsa, TypeScript turni kutilgan joydan oladi. <input onChange> qanday funksiya kutishini u biladi, demak e ning turini ham:

tsx
import { useState } from "react";

export default function App() {
  const [dish, setDish] = useState("");
  return (
    <div>
      <input
        aria-label="Taom"
        onChange={(e) => setDish(e.target.value)}
      />
      <p>Tanlandi: {dish}</p>
    </div>
  );
}

Ekranda:

text
Tanlandi: Manti

e ustiga sichqonchani olib borsangiz — ChangeEvent<HTMLInputElement, Element>. Bu — kontekstli tur (contextual typing): turni o'zgaruvchining o'zi emas, u qo'yilgan joy belgilaydi. Shuning uchun qisqa ishlovchilarni JSX ichida yozish TypeScript'da ham qulay.

2.2 Alohida funksiya — turni react dan

Ishlovchi uzun bo'lsa yoki bir necha joyda ishlatilsa, u alohida funksiyaga chiqadi. Endi kontekst yo'q — tur qo'lda yoziladi:

tsx
import { useState } from "react";
import type { ChangeEvent, MouseEvent } from "react";

export default function App() {
  const [dish, setDish] = useState("");

  function handleChange(event: ChangeEvent<HTMLInputElement>) {
    setDish(event.target.value);
  }

  function handleOrder(event: MouseEvent<HTMLButtonElement>) {
    const name = event.currentTarget.name;
    console.log("buyurtma:", dish, "| tugma:", name);
  }

  return (
    <div>
      <input aria-label="Taom" onChange={handleChange} />
      <button name="buyurtma" onClick={handleOrder}>
        Buyurtma
      </button>
    </div>
  );
}

Konsolda:

text
buyurtma: Lag'mon | tugma: buyurtma

Ekranda:

text
Buyurtma

Har hodisa turi — generic: <HTMLInputElement> ichidagi tur "hodisa qaysi elementda". Shuning uchun event.currentTarget.name — <button> ning name xususiyati, xatosiz. 17-qismda ko'rgan tuzoqni eslang: tur import qilinmasa, brauzerning global MouseEvent i olinadi va tsc xato beradi. import type { … } from "react" — majburiy.

Eng ko'p uchraydigan turlar:

Hodisa prop'i Tur Odatiy element
onChange ChangeEvent<HTMLInputElement> input, select, textarea
onClick MouseEvent<HTMLButtonElement> button, a
onKeyDown KeyboardEvent<HTMLInputElement> har qanday fokuslanadigan
onSubmit SubmitEvent<HTMLFormElement> form
onFocus, onBlur FocusEvent<HTMLInputElement> fokuslanadigan
onPointerDown PointerEvent<HTMLDivElement> har qanday
onDrop DragEvent<HTMLElement> tashlash zonasi

Turni yod olish shart emas. Eng tez yo'l: ishlovchini avval inline yozing, e ustiga sichqonchani olib boring, ko'rgan turni nusxalang.

2.3 target va currentTarget

Hodisa tarqalishi darsidan farqni bilasiz: currentTarget — ishlovchi ulangan element, target — haqiqatan bosilgan element (ichidagi <span> ham bo'lishi mumkin). TypeScript bu farqni turda ham saqlaydi:

tsx
import type { MouseEvent } from "react";

function handleClick(event: MouseEvent<HTMLButtonElement>) {
  console.log(event.target.textContent);
}

export default function App() {
  return (
    <button onClick={handleClick}>
      <b>Osh</b> — 35000
    </button>
  );
}

Xato:

text
TS2339: Property 'textContent' does not exist on type 'EventTarget'.

"textContent xususiyati EventTarget turida yo'q." target turi — umumiy EventTarget: u tugma ham, <b> ham, hatto matn tuguni ham bo'lishi mumkin. TypeScript buni bilmaydi va taxmin qilmaydi. currentTarget esa doim HTMLButtonElement — ishlovchini aynan unga uladingiz. Qoida: elementning xususiyati kerak bo'lsa — currentTarget. target kerak bo'lsa (delegatsiya) — avval tekshirish: if (event.target instanceof HTMLElement).

Bitta istisno — ChangeEvent. Unda target ham element turida (event.target.value xatosiz). Formadagi o'zgarish hodisasi ichma-ich bo'lmaydi, shuning uchun @types/react bu yerda target ni toraytirib bergan.

2.4 onSubmit: SubmitEvent va eski FormEvent

17-qismda forma ishlovchisini FormEvent<HTMLFormElement> deb yozgan edik. @types/react 19.3 da onSubmit ning aniqroq turi bor — SubmitEvent. Unda submitter — formani yuborgan tugma:

tsx
import type { SubmitEvent } from "react";

export default function App() {
  function handleSubmit(event: SubmitEvent<HTMLFormElement>) {
    event.preventDefault();
    const button = event.submitter;
    console.log("yuborgan tugma:", button?.textContent);
  }
  return (
    <form onSubmit={handleSubmit}>
      <button name="saqla">Saqlash</button>{" "}
      <button name="chiq">Saqlab chiqish</button>
    </form>
  );
}

Konsolda:

text
yuborgan tugma: Saqlab chiqish

Ekranda:

text
Saqlash Saqlab chiqish

FormEvent<HTMLFormElement> bilan yozilgan eski kod ham ishlaydi — u kengroq tur, submitter siz. FormEventHandler turi esa 19.3 da eskirgan (@deprecated): @types/react izohida "bunday hodisa aslida yo'q" deb yozilgan. Yangi kodda — SubmitEvent, ChangeEvent, InputEvent.

2.5 Hodisa prop'i — domen tilida

O'z komponentingizga hodisa prop'i qo'shganda, React hodisasini tashqariga uzatish shart emas. Ota komponentga odatda ma'no kerak, DOM hodisasi emas:

tsx
// ❌ ota DOM'ni bilishga majbur
type DishPickerProps = {
  onChange: (event: ChangeEvent<HTMLSelectElement>) => void;
};

// ✅ ota faqat natijani oladi
type DishPickerProps = {
  onPick: (dish: string) => void;
};

Ikkinchisi soddaroq va sinash oson: testda soxta hodisa yasash shart emas, onPick("Osh") yetadi. vazifalar dagi onAlmashtir(id), onOchir(id) — aynan shunday. Native element o'rami (keyingi bo'lim) — istisno: u <button> ning o'rnini bosadi, shuning uchun hodisalari ham aynan <button> nikidek.

Tekshirib ko'ring: onClick={handleOrder} ishlaydi, lekin const items = ["a"]; items.forEach(handleOrder) tsc xatosi beradi. Nega bir funksiya bir joyda to'g'ri, boshqasida xato?

Javob

handleOrder ning parametri MouseEvent<HTMLButtonElement>. onClick aynan shunday hodisa beradi — tur mos. forEach esa har elementni (string) uzatadi — string hodisa emas, mos kelmaydi. Turi yozilgan funksiya o'z "vilkasini" o'zi bilan olib yuradi va faqat mos "rozetkaga" tushadi.

3. Native atributlarni meros qilish

3.1 Muammo: o'ralgan tugma atributlarni yo'qotadi

Jasur akaning SaveButton iga qaytamiz:

tsx
type SaveButtonProps = { label: string; onClick: () => void };

function SaveButton({ label, onClick }: SaveButtonProps) {
  return <button onClick={onClick}>{label}</button>;
}

export default function App() {
  return (
    <SaveButton
      label="Saqlash"
      onClick={() => {}}
      disabled
      aria-label="Buyurtmani saqlash"
    />
  );
}

Xato:

text
TS2322: Type '{ label: string; onClick: () => void; disabled: true; "aria-label": string; }' is not assignable to type 'IntrinsicAttributes & SaveButtonProps'.
  Property 'disabled' does not exist on type 'IntrinsicAttributes & SaveButtonProps'.

"disabled SaveButtonProps da yo'q." Diqqat qiling: xato faqat disabled haqida, aria-label haqida emas. JSX'da chiziqcha bor atribut nomlari (aria-*, data-*) props turida topilmasa ham xato bermaydi — TypeScript'ning maxsus qoidasi. Bu qulay, lekin xavfli: aria-label o'tib ketdi, komponent esa uni <button> ga uzatmaydi. Malika baribir nomsiz tugmani eshitadi.

3.2 ComponentProps<"button"> va ...rest

Yechim — props turini <button> ning o'z turidan olish va qolgan hamma narsani elementga yoyish. Bu naqshni Komponent API'sini loyihalash darsida Tugma da ko'rgan edik — "o'rgatmang, uzating". U yerda maqsad API edi, bu yerda esa turlar tomoni: qaysi tur nimani beradi va qaysi biri tor.

tsx
import type { ComponentProps } from "react";

type SaveButtonProps = ComponentProps<"button"> & {
  tone?: "asosiy" | "xavfli";
};

function SaveButton({ tone = "asosiy", ...rest }: SaveButtonProps) {
  return <button data-tone={tone} {...rest} />;
}

export default function App() {
  return (
    <div>
      <SaveButton onClick={() => console.log("saqlandi")}>
        Saqlash
      </SaveButton>{" "}
      <SaveButton tone="xavfli" disabled aria-label="O'chirish">
        ✕
      </SaveButton>
    </div>
  );
}

Konsolda:

text
saqlandi

Ekranda:

text
Saqlash ✕

Nima bo'ldi:

  • ComponentProps<"button"> — <button> qabul qiladigan hamma narsa: onClick, disabled, type, aria-*, children, className va yana yuzlab.
  • & { tone?: … } — o'zimizning bitta qo'shimchamiz (kesishma).
  • { tone, ...rest } — tone ni olamiz, qolgani rest ga tushadi (rest). {...rest} — hammasi <button> ga.

children ham rest ichida — Saqlash matni <button> ga o'zi tushdi. Endi Jasur akaga yangi atribut kerak bo'lsa, komponentni o'zgartirish shart emas.

Tuzilmani diagrammada ko'ring — tur qanday yig'iladi:

flowchart LR
  A["ComponentProps&lt;'button'&gt;: 200+ atribut"] --> O["Omit: o'zgartiriladiganlar olib tashlanadi"]
  O --> K["& o'z props'imiz: variant, tone"]
  K --> P["Props turi"]
  P --> R["...rest → &lt;button {...rest}&gt;"]

Ikkinchi tugun (Omit) — ixtiyoriy qadam, «Omit: native prop'ni almashtirish» bo'limida.

3.3 WithRef, WithoutRef va React 19

ComponentProps ning ikki ukasi bor:

Tur ref bormi Qachon
ComponentProps<"button"> bor React 19 da odatiy tanlov
ComponentPropsWithRef<"button"> bor eski kodda, forwardRef bilan
ComponentPropsWithoutRef<"button"> yo'q ref ni o'zingiz alohida yozsangiz

React 19 gacha funksiya komponent ref ni prop sifatida ololmasdi — forwardRef kerak edi. Shuning uchun eski kodda ComponentPropsWithoutRef juda ko'p. React 19 da ref — oddiy prop (useRef II), demak ComponentProps<"button"> dagi ref ham ...rest bilan <button> ga yetib boradi.

3.4 HTMLAttributes — juda keng to'r

Internetda boshqa yozuvni ham uchratasiz: HTMLAttributes<HTMLButtonElement>. Nomiga qaraganda o'xshaydi, lekin u faqat umumiy atributlar (id, className, onClick, role). Elementga xos atributlar unda yo'q:

tsx
import type { HTMLAttributes } from "react";

function SaveButton(props: HTMLAttributes<HTMLButtonElement>) {
  return <button {...props} />;
}

export default function App() {
  return <SaveButton disabled>Saqlash</SaveButton>;
}

Xato:

text
TS2322: Type '{ disabled: true; children: string; }' is not assignable to type 'IntrinsicAttributes & HTMLAttributes<HTMLButtonElement>'.
  Property 'disabled' does not exist on type 'IntrinsicAttributes & HTMLAttributes<HTMLButtonElement>'.

disabled, type, form — ButtonHTMLAttributes da. value, placeholder — InputHTMLAttributes da. Qaysi biri qaysi interfeysda ekanini eslab qolish shart emas: ComponentProps<"button"> to'g'ri interfeysni o'zi tanlaydi. Qoida: o'ram komponent uchun — doim ComponentProps<"teg">. HTMLAttributes<HTMLDivElement> faqat div, section kabi o'ziga xos atributi yo'q teglar uchun mos.

3.5 {...rest} qayerda: tartib muhim

JSX'da keyingi atribut oldingisini bosadi — xuddi obyekt yoyishdagidek:

tsx
import type { ComponentProps, SubmitEvent } from "react";

function SafeButton(props: ComponentProps<"button">) {
  return <button type="button" {...props} />;
}

function LockedButton(props: ComponentProps<"button">) {
  return <button {...props} type="button" />;
}

export default function App() {
  function handleSubmit(event: SubmitEvent<HTMLFormElement>) {
    event.preventDefault();
    console.log("forma yuborildi:", event.submitter?.textContent);
  }
  return (
    <form onSubmit={handleSubmit}>
      <SafeButton type="submit">A</SafeButton>{" "}
      <LockedButton type="submit">B</LockedButton>
    </form>
  );
}

Konsolda:

text
forma yuborildi: A

Ekranda:

text
A B

Ikkala tugma bosildi, forma esa bir marta yuborildi. A — submit: tashqi qiymat {...props} orqali standartni bosdi. B — button: ichki qiymat oxirida turib, tashqisini bosdi. Ikkala tartib ham foydali:

  • Standart qiymat — atribut {...rest} dan oldin: chaqiruvchi xohlasa o'zgartiradi.
  • Majburiy qiymat — keyin: chaqiruvchi o'zgartira olmaydi.

className alohida holat: uni bosish emas, qo'shish kerak ("tugma " + className). Aks holda chaqiruvchi o'z klassini qo'shganda komponentning asosiy klassi yo'qoladi.

4. Omit: native prop'ni almashtirish

4.1 Muammo: bir xil nom, boshqa tur

Taom nomi maydoni uchun TextField yasaymiz. Ota komponentga hodisa emas, matn kerak: onChange(value: string). Lekin ComponentProps<"input"> da onChange allaqachon bor va u hodisa kutadi. Kesishma bilan ikkalasini birlashtirsak — tur ichida ziddiyat paydo bo'ladi: onChange bir vaqtda ikki xil funksiya bo'lishi kerak. Natijada bu prop'ni to'g'ri ishlatib bo'lmaydi.

Yechim — avval Omit bilan native onChange ni olib tashlash, keyin o'zimiznikini qo'shish:

tsx
import { useState } from "react";
import type { ComponentProps } from "react";

type TextFieldProps = Omit<ComponentProps<"input">, "onChange"> & {
  label: string;
  onChange: (value: string) => void;
};

function TextField({ label, onChange, id, ...rest }: TextFieldProps) {
  const inputId = id ?? "maydon";
  return (
    <p>
      <label htmlFor={inputId}>{label}</label>{" "}
      <input
        id={inputId}
        {...rest}
        onChange={(e) => onChange(e.target.value)}
      />
    </p>
  );
}

export default function App() {
  const [dish, setDish] = useState("");
  return (
    <div>
      <TextField
        label="Taom nomi"
        maxLength={40}
        onChange={setDish}
      />
      <p>Yangi taom: {dish}</p>
    </div>
  );
}

Ekranda:

text
Taom nomi
Yangi taom: Chuchvara

Uch narsaga qarang:

  • Omit<…, "onChange"> — native turdan bitta kalit olib tashlandi, qolgan 200+ atribut (maxLength, placeholder, required) joyida.
  • onChange={setDish} — setDish to'g'ridan-to'g'ri uzatildi. U satr qabul qiladi, bizning onChange ham satr beradi. Ota komponent e.target.value ni bilmaydi.
  • onChange {...rest} dan keyin yozildi. rest da endi onChange yo'q (destrukturizatsiya qildik), lekin tartib baribir "bu qiymat bosilmaydi" degan niyatni ko'rsatadi.

label — majburiy prop. Har maydonda yorliq bo'lishini tur kafolatlaydi: yorliqsiz <TextField onChange={…} /> — tsc xatosi. Bu — a11y'ni tur bilan himoyalash. Keyingi darsda bu g'oyani Tugma ga qo'llaymiz.

Tekshirib ko'ring: TextFieldProps da Omit ni olib tashlasak (ComponentProps<"input"> & { onChange: (value: string) => void }), onChange={setDish} qanday xato beradi?

Javob

Kesishmada onChange ning turi ikkalasining kesishmasi bo'ladi: funksiya ham hodisani, ham satrni qabul qila olishi kerak. setDish faqat satr (yoki updater funksiya) qabul qiladi. Hodisani qabul qilmaydi — tsc TS2322 beradi. Ichkarida esa onChange(e.target.value) chaqiruvi ham shubhali bo'ladi. Omit ziddiyatni ildizidan olib tashlaydi.

4.2 Bir nechta kalit va Pick

Omit ga bir necha kalitni union bilan berish mumkin: Omit<ComponentProps<"input">, "onChange" | "value" | "type">. Teskarisi — Pick: faqat kerakli kalitlarni olish. Pick<ComponentProps<"input">, "placeholder" | "maxLength"> — komponent faqat shu ikkitasini oladi. Pick — "kam va aniq" API (Komponent API'sini loyihalash), Omit — "deyarli hammasi, bittasi o'zgacha".

5. Vazifalar qadami: Tugma va useMenyu ichidagi turlar

refactor/turlar da bugun ham kod o'zgarmaydi. Bugungi darsning ikki g'oyasi vazifalar da 18/#30–#31 dan beri bor. Avval ui/Tugma.tsx ni qadam boshidagi holatida o'qiymiz (holat 33, kanondan aynan):

tsx
import type { ComponentPropsWithoutRef, Ref } from "react";

type TugmaVarianti = "oddiy" | "asosiy" | "xavfli";

type Props = ComponentPropsWithoutRef<"button"> & {
  variant?: TugmaVarianti;
  ref?: Ref<HTMLButtonElement> | undefined;
};

export function Tugma({
  variant = "oddiy",
  type = "button",
  className,
  ref,
  ...qolgan
}: Props) {
  return (
    <button
      ref={ref}
      type={type}
      className={
        className === undefined ? "tugma" : `tugma ${className}`
      }
      data-variant={variant === "oddiy" ? undefined : variant}
      {...qolgan}
    />
  );
}

Bugungi darsning hammasi shu yerda:

  • ComponentPropsWithoutRef<"button"> + o'z ref i. Natija ComponentProps<"button"> bilan bir xil. Kanon ref ni alohida yozishni tanladi — tur o'qilganda "ref qabul qiladi" ko'rinib tursin. | undefined — o'tgan darsdagi exactOptionalPropertyTypes uchun.
  • type = "button" — standart qiymat, va u {...qolgan} dan oldin. Formadagi tugma tasodifan submit bo'lmaydi, kerak bo'lsa chaqiruvchi type="submit" yozadi.
  • className — bosilmaydi, qo'shiladi.
  • data-variant — ko'rinish CSS'da (Dizayn tizimi asoslari).

Endi ui/useMenyu.ts dagi prop getter. Ishlovchilar JSX'dan tashqarida — obyekt ichida yozilgan, shuning uchun kontekst yo'q va turlar qo'lda (qisqartirilgan parcha):

ts
import type { KeyboardEvent, MouseEvent } from "react";

    tugmaXossalari: () => ({
      ref: tugmaRef,
      type: "button" as const,
      "aria-haspopup": "menu" as const,
      "aria-expanded": ochiq,
      onKeyDown: (event: KeyboardEvent<HTMLButtonElement>) => {
        if (event.key === "ArrowDown" || event.key === "ArrowUp") {
          event.preventDefault();
          och(event.key === "ArrowDown" ? "birinchi" : "oxirgi");
        }
      },
    }),

as const nega kerak? Usiz type: "button" ning turi string bo'lib qolardi. Obyekt <button {...menyu.tugmaXossalari()}> ga yoyilganda tsc string ni "button" | "submit" | "reset" ga bera olmasdi. Xuddi shunday "aria-haspopup" ham: unga faqat "menu", "listbox" kabi aniq qiymatlar mumkin.

Tekshirish — npm run tip: 0 xato. Bugun commit yo'q. Keyingi darsda Tugma ning props turiga Omit kiradi — tugmaning nomi bo'lishi shart bo'ladi.

6. Ko'p uchraydigan xatolar

Belgi Sabab Davosi
TS7006: Parameter 'event' implicitly has an 'any' type alohida ishlovchida tur yo'q ChangeEvent<HTMLInputElement> kabi tur
KeyboardEvent<…> is missing … isComposing tur react dan import qilinmagan import type { KeyboardEvent } from "react"
Property 'x' does not exist on type 'EventTarget' event.target currentTarget yoki instanceof
Property 'disabled' does not exist o'ramda native props yo'q ComponentProps<"button">
disabled HTMLAttributes da yo'q juda keng tur ComponentProps<"teg">
aria-label jim yo'qoldi chiziqchali nom tekshirilmaydi, {...rest} yo'q rest ni elementga yoying
onChange bilan ziddiyat kesishmada bir xil kalit Omit<…, "onChange">

7. Mashqlar

Mashqlar kurs/mashqlar/18/36-hodisalar-atributlar/ da.

1-mashq (oson): qaysi tur?

Ishlovchi alohida funksiyada. Turni yozing.

  • <select onChange={handlePick}> — handlePick(event: )
  • <form onSubmit={handleSend}> — handleSend(event: )
  • <textarea onKeyDown={handleKey}> — handleKey(event: )
Yechim
  • ChangeEvent<HTMLSelectElement> — event.target.value tanlangan <option> qiymati.
  • SubmitEvent<HTMLFormElement> — FormEvent<HTMLFormElement> ham qabul qilinadi (kengroq tur), lekin submitter faqat SubmitEvent da.
  • KeyboardEvent<HTMLTextAreaElement> — element turi textarea, input emas. Noto'g'ri element bersangiz, tsc onKeyDown={handleKey} qatorida xato beradi.

Hammasi import type { … } from "react" bilan.

2-mashq (o'rta): Badge — native span

Badge komponenti <span> ning hamma atributlarini qabul qilsin va qo'shimcha tone?: "yashil" | "qizil" (standart "yashil"). className qo'shilsin ("badge" + tashqi klass), tone — data-tone atributiga. Ishora: ComponentProps<"span">, destrukturizatsiyada className va tone ni ajrating («{...rest} qayerda: tartib muhim» bo'limi).

tsx
export default function App() {
  return (
    <p>
      Osh <Badge>yangi</Badge>{" "}
      <Badge tone="qizil" title="Bugun tugadi" className="kichik">
        tugadi
      </Badge>
    </p>
  );
}
Yechim
tsx
import type { ComponentProps } from "react";

type BadgeProps = ComponentProps<"span"> & {
  tone?: "yashil" | "qizil";
};

function Badge({ tone = "yashil", className, ...rest }: BadgeProps) {
  const classes =
    className === undefined ? "badge" : `badge ${className}`;
  return <span className={classes} data-tone={tone} {...rest} />;
}

export default function App() {
  return (
    <p>
      Osh <Badge>yangi</Badge>{" "}
      <Badge tone="qizil" title="Bugun tugadi" className="kichik">
        tugadi
      </Badge>
    </p>
  );
}

Ekranda:

text
Osh yangi tugadi

title, children — rest orqali <span> ga yetdi, Badge ularni sanab o'tirmadi. className destrukturizatsiyada olingani uchun rest da yo'q — {...rest} uni bosib ketolmaydi. Natijada ikkinchi badge klassi — "badge kichik".

3-mashq (qiyin): NumberField — Omit bilan

Mehmonlar soni maydoni. Talablar:

  1. Props — <input> ning hamma atributlari, lekin type, value, onChange o'zimizniki: value: number, onChange: (value: number) => void. type umuman berilmaydi (doim "number").
  2. label: string — majburiy, <label htmlFor> bilan bog'langan.
  3. App — useState(2), maydon min={1}, max={12}, ostida Mehmonlar: N.

Ishora: Omit<…, "type" | "value" | "onChange">; e.target.valueAsNumber — son (Formalar I).

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

type NumberFieldProps = Omit<
  ComponentProps<"input">,
  "type" | "value" | "onChange"
> & {
  label: string;
  value: number;
  onChange: (value: number) => void;
};

function NumberField(props: NumberFieldProps) {
  const { label, value, onChange, id = "son", ...rest } = props;
  return (
    <p>
      <label htmlFor={id}>{label}</label>{" "}
      <input
        {...rest}
        id={id}
        type="number"
        value={value}
        onChange={(e) => onChange(e.target.valueAsNumber)}
      />
    </p>
  );
}

export default function App() {
  const [guests, setGuests] = useState(2);
  return (
    <div>
      <NumberField
        label="Mehmonlar"
        min={1}
        max={12}
        value={guests}
        onChange={setGuests}
      />
      <p>Mehmonlar: {guests}</p>
    </div>
  );
}

Ekranda:

text
Mehmonlar
Mehmonlar: 5

type="number" {...rest} dan keyin — lekin Omit tufayli rest da type umuman yo'q, chaqiruvchi uni bera olmaydi (<NumberField type="text" …> — tsc xatosi). min, max esa native — rest orqali o'tdi. Bo'sh maydonda valueAsNumber — NaN. Haqiqiy formada buni tekshirish kerak: Number.isNaN(n) ? … : onChange(n).

4-mashq: Vazifalar qadami

Tugma.tsx va useMenyu.ts ni o'qing. Savollarga javob bering: (a) Tugma ga type berilmasa nima bo'ladi va nega bu standart xavfsiz? (b) className="kichik" berilsa, <button> ning class atributi qanday? (c) useMenyu dagi type: "button" as const dan as const olib tashlansa, qaysi buyruq yiqiladi?

Yechim
  • (a) type="button" — formada tugma bosilganda forma yuborilmaydi. HTML'da <button> ning standarti submit, bu ko'p xatoga sabab bo'ladi (Formalar I).
  • (b) "tugma kichik" — className qo'shiladi, bosilmaydi.
  • (c) npm run tip (tsc): {...tugmaXossalari()} yoyilgan joyda string turini "button" | "submit" | "reset" | undefined ga berib bo'lmaydi. Lint va test o'tishi mumkin, tur tekshiruvi — yo'q.

8. Real ishda

  • Dizayn tizimlari (shadcn/ui, Radix, MUI) — har tugma, maydon ComponentProps<"button"> yoki shunga o'xshash tur bilan. Ularning kodini ochsangiz, bugungi ...props naqshini ko'rasiz.
  • Forma kutubxonalari (React Hook Form) maydonga {...register("ism")} yoyadi — bu ham prop getter, ichida onChange, onBlur, ref, name (RHF).
  • Kod ko'rigida tez-tez uchraydigan izoh: "bu ishlovchi DOM hodisasini otaga uzatyapti — onPick(value) qiling".
  • Intervyu savollari: "target va currentTarget turlari nega farq qiladi?", "ComponentProps va HTMLAttributes farqi?", "native prop'ni qanday almashtirasiz?" (javob: Omit).

Xulosa

  • Inline ishlovchida tur kerak emas (kontekstli tur); alohida funksiyada — react dan ChangeEvent, MouseEvent, KeyboardEvent, SubmitEvent.
  • currentTarget — ishlovchi ulangan element (aniq tur), target — EventTarget (tekshirish kerak).
  • O'ram komponent — ComponentProps<"teg"> + ...rest; HTMLAttributes elementga xos atributlarni bilmaydi.
  • {...rest} dan oldin — standart, keyin — majburiy qiymat; className — qo'shiladi.
  • Native prop'ni o'zgacha qilish — Omit<…, "onChange"> & { onChange: … }.
  • vazifalar: Tugma — ComponentPropsWithoutRef<"button"> + ref, type="button" standart; useMenyu — as const va qo'lda hodisa turlari.

Keyingi dars: React + TS IV: generic, polymorphic va discriminated union props — generic komponentlar va bir-birini istisno qiladigan props.

Manbalar

  • React: "Using TypeScript — Typing DOM events" — react.dev/learn/typescript (2026-10 holatiga, React 19.3)
  • @types/react 19.3.0 — SubmitEvent, ChangeEvent, FormEventHandler (@deprecated), ComponentProps ta'riflari
  • TypeScript: JSX — "attribute names that are not valid identifiers" — typescriptlang.org/docs/handbook/jsx.html
  • MDN: SubmitEvent.submitter — developer.mozilla.org/docs/Web/API/SubmitEvent/submitter
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
React + TS III: hodisalar va HTML atributlari — ChangeEvent, ComponentProps va Omit — IlmHamroh