Mundarija (28)
- Bu darsda
- 1. Nega bu kerak?
- 2. Generic komponentlar
- 2.1 Muammo: unknown yoki takrorlash
- 2.2 Cheklov: T extends …
- 2.3 .tsx dagi <T,> tuzog'i
- 2.4 Tur argumentini ochiq berish
- 3. Discriminated union props
- 3.1 Ajratuvchi maydon bilan
- 3.2 Tuzoq: destrukturizatsiya union'ni "buzadi"
- 3.3 Ajratuvchisiz: ?: never bilan istisno
- 4. Polymorphic komponentlar: as prop
- 4.1 G'oya
- 4.2 Turi
- 4.3 Narxi
- 5. satisfies: tekshir, lekin turini o'zgartirma
- 6. Vazifalar qadami: Tugma — nomi bo'lishi shart
- 6.1 Tur o'zgarishi
- 6.2 O'lchov
- 7. Ko'p uchraydigan xatolar
- 8. Mashqlar
- 1-mashq (oson): o'tadimi?
- 2-mashq (o'rta): generic Select
- 3-mashq (qiyin): Notice — uch xil xabar
- 4-mashq: Vazifalar qadami
- 9. Real ishda
- Xulosa
- Manbalar
React + TS IV: generic, polymorphic va discriminated union props — xatoni kompilyatsiyada ushlash
Qisqacha: Generic komponent (
function List<T>(props: ListProps<T>)) bir xil kodni har xil ma'lumot turi bilan ishlatadi va turni yo'qotmaydi:items— taomlar bo'lsa,renderItemham taom oladi. Discriminated union props esa "yo bu, yo u" qoidasini turga yozadi:{ kind: "fixed"; amount } | { kind: "range"; min; max }. Ajratuvchi maydon bo'lmasa —?: neverbilan istisno:{ children } | { children?: never; "aria-label": string }. Polymorphic komponentasprop'iga qarab boshqa teg bo'ladi va atributlarni shu tegga mos tekshiradi.satisfiesesa obyektni turga solishtiradi, lekin uning aniq turini saqlab qoladi.
Bu darsda
- Generic komponent yozasiz va
.tsxdagi<T,>tuzog'ini bilasiz. - Bir-birini istisno qiladigan props'ni discriminated union va
neverbilan yozasiz. - Destrukturizatsiya qilinganda union nega "buziladi"ni ko'rasiz.
- Polymorphic
asprop'ini turlab,<Text as="a" href>ni to'g'ri,<Text as="span" href>ni xato qilasiz. vazifalardagiTugmaga "nomi bo'lishi shart" qoidasini tur bilan qo'shasiz.
Oldin bilishingiz kerak: React + TS III, Generics, Discriminated union, Polymorphic komponentlar.
1. Nega bu kerak?
Oldingi darsda ComponentProps<"button"> bilan tugmani "standart rozetka" qildik. Endi u har qanday props'ni qabul qiladi — hatto ma'nosiz kombinatsiyalarni ham. Ikki voqea:
- Sardor menyuga yopish tugmasini qo'shdi:
<Tugma onClick={yop}><XIcon /></Tugma>. Ichida faqat ikonka, matn yo'q. Ko'zi ko'radigan foydalanuvchi "" ni tushunadi. Malika esa ekran o'quvchida "tugma" degan bitta so'zni eshitadi. Qaysi tugma, nima qiladi — noma'lum. - Jasur aka narx komponentiga
amountham,min/maxham berdi. Komponent qaysi birini ko'rsatishni bilmay, birinchisini tanladi. Menyuda noto'g'ri narx chiqdi.
Ikkala xato ham ish vaqtida, foydalanuvchida topildi. Bu darsda ularni tsc topadi.
Hayotiy o'xshatish: poyezd chiptasi. Chiptada yo "o'tirish joyi", yo "yotish joyi" bo'ladi — ikkalasi bir vaqtda emas, hech biri yo'q ham emas. Kassa dasturi bunday chiptani chiqarishga yo'l qo'ymaydi. Union props — kassaning shu qoidasi.
2. Generic komponentlar
2.1 Muammo: unknown yoki takrorlash
«Bahor» da ro'yxatlar ko'p: taomlar, stollar, buyurtmalar. Har biri uchun alohida DishList, TableList yozish — takrorlash. Bitta List yozib, items: unknown[] desak — tur yo'qoladi: renderItem ichida item.name xato bo'ladi. Generics darsidagi yechim komponentga ham ishlaydi:
import type { ReactNode } from "react";
type ListProps<T> = {
items: readonly T[];
getKey: (item: T) => string | number;
renderItem: (item: T) => ReactNode;
};
function List<T>({ items, getKey, renderItem }: ListProps<T>) {
return (
<ul>
{items.map((item) => (
<li key={getKey(item)}>{renderItem(item)}</li>
))}
</ul>
);
}
type Dish = { id: number; name: string; price: number };
const dishes: Dish[] = [
{ id: 1, name: "Osh", price: 35000 },
{ id: 4, name: "Manti", price: 30000 },
];
export default function App() {
return (
<div>
<List
items={dishes}
getKey={(d) => d.id}
renderItem={(d) => `${d.name} — ${d.price} so'm`}
/>
<List
items={[5, 7]}
getKey={(n) => n}
renderItem={(n) => `${n}-stol bo'sh`}
/>
</div>
);
}Ekranda:
Osh — 35000 so'm
Manti — 30000 so'm
5-stol bo'sh
7-stol bo'sh<List items={dishes} …> da T ni hech kim yozmadi. TypeScript uni items dan chiqardi: T = Dish. Shundan keyin getKey={(d) => …} va renderItem={(d) => …} dagi d ham Dish — d.name xatosiz, d.nmae esa xato. Ikkinchi ro'yxatda T = number. Bir komponent — ikki xil, lekin har biri to'liq turlangan.
Generic komponentni React.FC bilan yozib bo'lmasligini React + TS I da aytgan edik: const List: FC<ListProps<T>> — T qayerdan keladi? Oddiy function bunda tabiiy.
2.2 Cheklov: T extends …
Komponent T haqida biror narsani bilishi kerak bo'lsa — cheklov. Masalan, getKey ni har safar berish zerikarli, ro'yxat elementida baribir id bor:
type ListProps<T extends { id: number }> = {
items: readonly T[];
renderItem: (item: T) => string;
};
function List<T extends { id: number }>({
items,
renderItem,
}: ListProps<T>) {
return (
<ul>
{items.map((item) => (
<li key={item.id}>{renderItem(item)}</li>
))}
</ul>
);
}
export default function App() {
const tables = [{ id: 5, seats: 4 }, { id: 7, seats: 2 }];
return (
<List
items={tables}
renderItem={(t) => `${t.id}: ${t.seats} o'rin`}
/>
);
}Ekranda:
5: 4 o'rin
7: 2 o'rinitem.id — komponent ichida xatosiz, chunki cheklov kafolatlaydi. t.seats esa tashqarida ham bor — T toraymadi, faqat "kamida id bor" deyildi. id siz massiv bersangiz (items={["Osh"]}) — tsc xatosi.
2.3 .tsx dagi <T,> tuzog'i
Generic funksiyani strelka bilan yozmoqchi bo'lsangiz, .tsx da kutilmagan xato chiqadi:
const first = <T>(items: T[]) => items[0];
console.log(first([1]));Xato:
TS17008: JSX element 'T' has no corresponding closing tag.
TS1382: Unexpected token. Did you mean `{'>'}` or `>`?
TS1005: '</' expected."JSX elementi T ning yopuvchi tegi yo'q." .tsx faylda <T> — JSX tegi boshlanishi bilan bir xil ko'rinadi, tahlilchi uni <div> kabi o'qidi. Yechimlar:
<T,>— vergul "bu teg emas, tur parametrlari ro'yxati" deb ko'rsatadi:const first = <T,>(items: T[]) => items[0];<T extends unknown>— xuddi shu ta'sir.- Eng oddiysi —
function first<T>(items: T[]) { … }. Bu kursda shunday.
2.4 Tur argumentini ochiq berish
Ba'zan chiqarish kerakli turni bermaydi — masalan, boshlang'ich ro'yxat bo'sh. JSX'da ham tur argumentini yozish mumkin:
<List<Dish>
items={[]}
getKey={(d) => d.id}
renderItem={(d) => d.name}
/>Bo'sh [] dan T ni chiqarib bo'lmaydi — <List<Dish> uni aniq aytadi. Bu useState<string[]>([]) dagi muammoning JSX'dagi ko'rinishi.
Tekshirib ko'ring:
ListningrenderItemturi(item: T) => ReactNodeo'rniga(item: unknown) => ReactNodebo'lsa,renderItem={(d) => d.name}da nima bo'ladi?
Javob
d turi unknown bo'ladi va d.name — TS18046: 'd' is of type 'unknown'. items va renderItem orasidagi bog'lanish uzildi: TypeScript renderItem ga aynan items dagi element tushishini bilmaydi. Generic T aynan shu bog'lanishni ifodalaydi: "kirgan tur — chiqqan tur".
3. Discriminated union props
3.1 Ajratuvchi maydon bilan
Jasur akaning narx komponenti. Narx — yo aniq (35000), yo oraliq (28000–35000, porsiyaga qarab). Ikkalasi bir vaqtda ma'nosiz:
type PriceProps =
| { kind: "fixed"; amount: number }
| { kind: "range"; min: number; max: number };
function Price(props: PriceProps) {
if (props.kind === "fixed") {
return <p>{props.amount} so'm</p>;
}
return (
<p>
{props.min}–{props.max} so'm
</p>
);
}
export default function App() {
return (
<div>
<Price kind="fixed" amount={35000} />
<Price kind="range" min={28000} max={35000} />
</div>
);
}Ekranda:
35000 so'm
28000–35000 so'mBu — Discriminated union darsidagi naqsh, faqat props'da. kind — ajratuvchi (discriminant). props.kind === "fixed" dan keyin props.amount bor, props.min yo'q. Endi Jasur akaning xatosi:
type PriceProps =
| { kind: "fixed"; amount: number }
| { kind: "range"; min: number; max: number };
function Price(props: PriceProps) {
return <p>{props.kind}</p>;
}
export default function App() {
return (
<div>
<Price kind="fixed" min={28000} />
<Price kind="range" min={28000} max={35000} amount={30000} />
</div>
);
}Xato:
TS2322: Type '{ kind: "fixed"; min: number; }' is not assignable to type 'IntrinsicAttributes & PriceProps'.
Property 'min' does not exist on type 'IntrinsicAttributes & { kind: "fixed"; amount: number; }'.
TS2322: Type '{ kind: "range"; min: number; max: number; amount: number; }' is not assignable to type 'IntrinsicAttributes & PriceProps'.
Property 'amount' does not exist on type 'IntrinsicAttributes & { kind: "range"; min: number; max: number; }'.Xabarlarga qarang: tsc kind ga qarab qaysi variant ekanini tanladi va aynan shu variantga solishtirdi. kind="fixed" — min yo'q. kind="range" — amount yo'q. Noto'g'ri narx endi menyuga chiqmaydi — build'dan o'tmaydi.
3.2 Tuzoq: destrukturizatsiya union'ni "buzadi"
Props'ni har doim destrukturizatsiya qilib o'rgangansiz. Union props bilan bu xavfli:
type PriceProps =
| { kind: "fixed"; amount: number }
| { kind: "range"; min: number; max: number };
function Price({ kind, ...rest }: PriceProps) {
if (kind === "fixed") {
return <p>{rest.amount} so'm</p>;
}
return <p>{rest.min} so'mdan</p>;
}
export default function App() {
return <Price kind="fixed" amount={35000} />;
}Xato:
TS2339: Property 'amount' does not exist on type '{ amount: number; } | { min: number; max: number; }'.
Property 'amount' does not exist on type '{ min: number; max: number; }'.
TS2339: Property 'min' does not exist on type '{ amount: number; } | { min: number; max: number; }'.
Property 'min' does not exist on type '{ amount: number; }'.kind va rest — endi ikki alohida o'zgaruvchi. kind === "fixed" tekshiruvi kind ni toraytiradi, lekin rest haqida TypeScript hech narsa bilmaydi. Bog'lanish uzildi. Qoida: union props'ni avval butun holida (props) tekshiring, keyin tarmoq ichida destrukturizatsiya qiling. TypeScript faqat bitta holatni kuzatadi: barcha xususiyat oddiy const ga yechilsa ({ kind, amount } — rest siz), u ularni bog'liq deb biladi. ...rest bilan — yo'q.
3.3 Ajratuvchisiz: ?: never bilan istisno
Ba'zan ajratuvchi maydon yo'q va qo'shib bo'lmaydi. Sardorning tugmasi: nomi yo matn (children) bilan, yo aria-label bilan. kind="matnli" deb yozish ortiqcha. Bunday union'da "bu variantda bu xususiyat bo'lmasin" qoidasini never ifodalaydi:
type Name =
| { children: ReactNode; "aria-label"?: string } // matnli
| { children?: never; "aria-label": string }; // faqat ikonkaIkkinchi variantni o'qing: "children berilsa, turi never bo'lishi kerak". never turidagi qiymat yo'q — demak children umuman berilmasligi kerak. Va aria-label majburiy. Natija jadvalda:
| Berilgan | 1-variant | 2-variant | Natija |
|---|---|---|---|
children |
(never) |
o'tadi | |
aria-label |
(children yo'q) |
o'tadi | |
| ikkalasi | o'tadi | ||
| hech biri | TS2322 |
Ikkalasi berilganda ham o'tadi: matnli tugmaga qo'shimcha aria-label ba'zan kerak (masalan, " Yopish" matni bor, lekin ekran o'quvchiga "Savatni yopish" aniqroq). Faqat "hech biri" — taqiqlangan.
flowchart TD
A["<Tugma … />"] --> B{"children bormi?"}
B -- "ha" --> C["1-variant: o'tadi"]
B -- "yo'q" --> D{"aria-label bormi?"}
D -- "ha" --> E["2-variant: o'tadi"]
D -- "yo'q" --> F["TS2322: nomsiz tugma"]Diagrammada TS2322 ga olib boradigan yagona yo'l — Malika eshitadigan "nomsiz tugma". Endi u build'da to'xtaydi. Haqiqiy xabarni «Vazifalar qadami» bo'limida, vazifalar dagi o'lchovda ko'ramiz.
Tekshirib ko'ring: Nega 2-variantda
children?: neveryozildi? Shunchaki{ "aria-label": string }bo'lsa nima o'zgaradi?
Javob
Union "ochiq": { "aria-label": string } varianti children haqida hech narsa demaydi. TypeScript'ning ortiqcha xususiyat tekshiruvi union'da yumshoqroq ishlaydi — u boshqa variantlardagi kalitlarni "tanish" deb biladi. Natijada noto'g'ri kombinatsiyalar o'tib ketishi va narrowing ("children" in props) chalkash bo'lishi mumkin. children?: never qoidani ochiq yozadi: bu variantda children yo'q. Bu ikki variantni bir-biridan aniq ajratadi.
4. Polymorphic komponentlar: as prop
4.1 G'oya
Polymorphic komponentlar darsida as prop'ini ko'rgan edik: <Text as="h2"> — sarlavha, <Text as="a" href> — havola, ko'rinishi bir xil. U yerda asosiy savol "nega kerak" edi va turning birinchi, qisqa variantini ko'rdik (ElementType + ComponentPropsWithoutRef<T>). Bugun uni generic bilimi bilan oxirigacha o'qiymiz: standart teg, children va xato xabarining har qismi. Maqsad o'sha: as="a" bo'lsa, href ruxsat etilsin, as="span" bo'lsa — yo'q.
4.2 Turi
as ning qiymati — teg nomi yoki komponent. Bu tur react da bor: ElementType. Props esa shu tegning props'i — ComponentProps<E>:
import type { ComponentProps, ElementType, ReactNode } from "react";
type TextProps<E extends ElementType> = {
as?: E;
children: ReactNode;
} & Omit<ComponentProps<E>, "as" | "children">;
function Text<E extends ElementType = "span">({
as,
...rest
}: TextProps<E>) {
const Tag = as ?? "span";
return <Tag className="matn" {...rest} />;
}
export default function App() {
return (
<div>
<Text as="h2">Bugungi menyu</Text>
<Text as="a" href="https://example.com/menyu">
To'liq menyu
</Text>{" "}
<Text>yangilangan</Text>
</div>
);
}Ekranda:
Bugungi menyu
To'liq menyu yangilanganQadamma-qadam:
E extends ElementType = "span"— tur parametri, standart —"span".asberilmasa,E = "span".<Text as="a" …>— TypeScriptasdanE = "a"ni chiqardi. Props turi endiComponentProps<"a">ni o'z ichiga oladi —hrefruxsat.const Tag = as ?? "span"— JSX'da katta harfli o'zgaruvchi komponent sifatida ishlatiladi (JSX aslida JavaScript).
Noto'g'ri kombinatsiya:
import type { ComponentProps, ElementType, ReactNode } from "react";
type TextProps<E extends ElementType> = {
as?: E;
children: ReactNode;
} & Omit<ComponentProps<E>, "as" | "children">;
function Text<E extends ElementType = "span">({
as,
...rest
}: TextProps<E>) {
const Tag = as ?? "span";
return <Tag {...rest} />;
}
export default function App() {
return (
<Text as="span" href="https://example.com/menyu">
Menyu
</Text>
);
}Xato:
TS2322: Type '{ as: "span"; href: string; children: string; }' is not assignable to type 'IntrinsicAttributes & { as?: "span" | undefined; children: ReactNode; } & Omit<DetailedHTMLProps<HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, "as" | "children">'.
Property 'href' does not exist on type 'IntrinsicAttributes & { as?: "span" | undefined; children: ReactNode; } & Omit<DetailedHTMLProps<HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, "as" | "children">'. Did you mean 'ref'?Xabar uzun, lekin oxirgi qismi aniq: "href <span> ning props'ida yo'q. ref ni nazarda tutdingizmi?". <span> da href — ma'nosiz, havola bo'lmaydi va klaviatura bilan ochilmaydi.
4.3 Narxi
Polymorphic turlar kuchli, lekin qimmat:
- Xato xabarlari uzun — yuqoridagidek. Jamoadoshingiz uni o'qishga qiynaladi.
- tsc sekinlashadi. Har
<Text as=…>uchunComponentProps<E>qayta hisoblanadi. Katta loyihada (yuzlab ishlatilish) bu soniyalarda sezilarli bo'lishi mumkin. refturi murakkablashadi:as="a"daHTMLAnchorElement,as="button"da boshqa.
Shuning uchun ko'p dizayn tizimlari as o'rniga asChild naqshini tanladi (Radix, shadcn/ui): komponent o'z tegini emas, bolaning tegini ishlatadi (Polymorphic komponentlar). vazifalar da polymorphic komponent yo'q — kerak bo'lmadi. Qoida: as ni faqat haqiqatan bir necha xil teg kerak bo'lganda qo'llang (tipografiya, Box, Stack).
5. satisfies: tekshir, lekin turini o'zgartirma
Komponent atrofida ko'pincha konfiguratsiya obyektlari bo'ladi: variant → rang, holat → matn. Ularni turga solishtirish kerak — hamma kalit bormi? Lekin tur annotatsiyasi (: Record<…>) obyektning aniq turini "kengaytirib" yuboradi. satisfies ikkalasini beradi:
type Variant = "oddiy" | "asosiy" | "xavfli";
const colors = {
oddiy: "#e8efe9",
asosiy: "#1f7a4d",
} satisfies Record<Variant, string>;
export default function App() {
return <p style={{ color: colors.asosiy }}>Saqlash</p>;
}Xato:
TS2741: Property 'xavfli' is missing in type '{ oddiy: string; asosiy: string; }' but required in type 'Record<Variant, string>'."xavfli yetishmayapti." Yangi variant qo'shilsa — rang yozish unutilmaydi. Nega const colors: Record<Variant, string> = … emas? Annotatsiya bilan colors turi — Record<Variant, string>, ya'ni "har kalit — qandaydir satr". satisfies bilan esa tur — obyektning o'zi: { oddiy: string; asosiy: string; xavfli: string }. Qiymatlar literal bo'lishi kerak bo'lsa, as const satisfies … qo'shiladi. Farq kichik ko'rinadi, lekin konfiguratsiyadan tur chiqarishda (keyof typeof colors) muhim.
| Yozuv | Tekshiradimi | Obyekt turi |
|---|---|---|
const c = {…} |
yo'q | aniq |
const c: Record<V, string> = {…} |
ha | kengaygan |
const c = {…} satisfies Record<V, string> |
ha | aniq |
6. Vazifalar qadami: Tugma — nomi bo'lishi shart
6.1 Tur o'zgarishi
refactor/turlar dagi to'rtinchi bo'lak — birinchi haqiqiy kod o'zgarishi. ui/Tugma.tsx da props turi (kanondan aynan):
import type { ComponentPropsWithoutRef, ReactNode, Ref } from "react";
type TugmaVarianti = "oddiy" | "asosiy" | "xavfli";
// 18/#37: discriminated union — tugmaning NOMI bo'lishi shart (ekran
// o'quvchi uchun): yo matn (children), yo aria-label. Matnsiz va
// aria-label'siz <Tugma /> — tsc xatosi (o'lchandi, natijalar/38-*)
type Nomi =
| { children: ReactNode; "aria-label"?: string }
| { children?: never; "aria-label": string };
type Props = Omit<
ComponentPropsWithoutRef<"button">,
"children" | "aria-label"
> &
Nomi & {
variant?: TugmaVarianti;
ref?: Ref<HTMLButtonElement> | undefined;
};Uch darsning bilimi bir joyda: ComponentPropsWithoutRef (36-dars), Omit — native children va aria-label olib tashlanadi, chunki ular endi Nomi da boshqacha turlangan. Nomi — bugungi never bilan istisno. Komponentning tanasi o'zgarmadi — o'zgarish faqat turda.
6.2 O'lchov
Tur ishlashini kanon vaqtinchalik fayl bilan tekshirdi — uch xil tugma (natijalar/38-turlar.txt):
import { Tugma } from "./ui/Tugma.tsx";
export const nomsiz = <Tugma onClick={() => {}} />;
export const belgili = <Tugma aria-label="Yopish" onClick={() => {}} />;
export const matnli = <Tugma>Saqlash</Tugma>;Haqiqiy natija — faqat bitta xato, nomsiz da:
src/sinov-tugma.tsx(2,24): error TS2322: Type '{ onClick: () => void; }' is not assignable to type 'IntrinsicAttributes & Props'.
Property '"aria-label"' is missing in type '{ onClick: () => void; }' but required in type '{ children?: never; "aria-label": string; }'."{ onClick } turida aria-label yo'q, lekin { children?: never; "aria-label": string } da u majburiy." tsc ikkinchi variantni tanladi (children yo'q) va unga nima yetishmasligini aytdi. Xabardagi children?: never — exactOptionalPropertyTypes yoqilgani uchun aynan shunday ko'rinadi (bayroqsiz tsc uni children?: undefined deb yozardi).
Shu xatoni o'zingiz ham jonli ko'ring — kanondagi turning qisqa nusxasi:
// @ts-options: {"exactOptionalPropertyTypes": true}
import type { ComponentProps, ReactNode } from "react";
type Name =
| { children: ReactNode; "aria-label"?: string }
| { children?: never; "aria-label": string };
type ButtonProps = Omit<
ComponentProps<"button">,
"children" | "aria-label"
> &
Name;
function Button(props: ButtonProps) {
return <button type="button" {...props} />;
}
export default function App() {
return <Button onClick={() => {}} />;
}Xato:
TS2322: Type '{ onClick: () => void; }' is not assignable to type 'IntrinsicAttributes & ButtonProps'.
Property '"aria-label"' is missing in type '{ onClick: () => void; }' but required in type '{ children?: never; "aria-label": string; }'.Kanondagi 9 ta Tugma ishlatilishi o'zgarishsiz o'tdi — hammasida matn bor edi. Demak qoida bugun hech narsani buzmadi, ertangi nomsiz tugmani esa to'xtatadi. 39-darsdagi axe tekshiruvi buni ish vaqtida ham tasdiqlaydi (React'da accessibility asoslari).
Tekshiruvlar: npm run tip — 0 xato (vaqtinchalik fayl olib tashlangach), npm run lint, npm test — toza. Commit hali yo'q — keyingi darsda.
7. Ko'p uchraydigan xatolar
| Belgi | Sabab | Davosi |
|---|---|---|
TS17008: JSX element 'T' has no corresponding closing tag |
.tsx da <T>(…) => |
<T,> yoki function |
renderItem da 'd' is of type 'unknown' |
generic o'rniga unknown |
List<T>, (item: T) => … |
TS2339 … does not exist on type '{…} | {…}' |
union props ...rest bilan destrukturizatsiya |
avval props.kind ni tekshiring |
| ikki variant aralashib o'tdi | ?: never yo'q |
istisno variantga x?: never |
uzun Omit<DetailedHTMLProps<…>> xabari |
polymorphic as |
oxirgi qatorni o'qing; asChild ni ko'ring |
TS2741 … is missing … Record<…> |
satisfies — kalit yetishmaydi |
kalitni qo'shing |
8. Mashqlar
Mashqlar kurs/mashqlar/18/37-generic-union/ da.
1-mashq (oson): o'tadimi?
type Name = { children: ReactNode; "aria-label"?: string } | { children?: never; "aria-label": string }. ha yoki yo'q:
<Button>Saqlash</Button><Button aria-label="Yopish" /><Button /><Button aria-label="Savatni yopish">✕ Yopish</Button>
Yechim
- ha — 1-variant:
childrenbor. - ha — 2-variant:
childrenyo'q,aria-labelbor. - yo'q — hech bir variant: 1-variantda
childrenmajburiy, 2-variantdaaria-label. - ha — 1-variant:
childrenbor,aria-labelixtiyoriy va ruxsat. 2-variantga to'g'ri kelmaydi (children—never), lekin bitta variant yetarli.
2-mashq (o'rta): generic Select
Generic Select<T> yozing: options: readonly T[], getLabel: (o: T) => string, onPick: (o: T) => void, label: string. Ichida <label> + <select>, qiymat — indeks (String(i)). Tanlanganda onPick(options[i]) chaqirilsin. App — taomlar ({ name, price }), tanlansa konsolga tanlandi: Manti 30000. Ishora: Number(e.target.value) — indeks; options[i] turi T | undefined bo'lishi mumkin (noUncheckedIndexedAccess bo'lsa) — tekshiring.
Yechim
type SelectProps<T> = {
label: string;
options: readonly T[];
getLabel: (option: T) => string;
onPick: (option: T) => void;
};
function Select<T>(props: SelectProps<T>) {
const { label, options, getLabel, onPick } = props;
return (
<p>
<label htmlFor="tanlov">{label}</label>{" "}
<select
id="tanlov"
onChange={(e) => {
const option = options[Number(e.target.value)];
if (option !== undefined) onPick(option);
}}
>
{options.map((o, i) => (
<option key={i} value={i}>
{getLabel(o)}
</option>
))}
</select>
</p>
);
}
const dishes = [
{ name: "Osh", price: 35000 },
{ name: "Manti", price: 30000 },
];
export default function App() {
return (
<Select
label="Taom"
options={dishes}
getLabel={(d) => d.name}
onPick={(d) => console.log("tanlandi:", d.name, d.price)}
/>
);
}Konsolda:
tanlandi: Manti 30000Ekranda:
Taom OshMantionPick={(d) => …} dagi d — { name: string; price: number }, options dan chiqarildi. T ni hech qayerda yozmadik. Ekrandagi OshManti — <option> lar matni, tekshiruvchi ularni bitta qatorda ko'rsatadi. key={i} — bu yerda maqbul: ro'yxat o'zgarmaydi (key).
3-mashq (qiyin): Notice — uch xil xabar
Notice komponenti uch turdan birini oladi:
{ kind: "info"; text: string }— oddiy<p>.{ kind: "error"; text: string; retry: () => void }—role="alert"li<p>va «Qayta urinish» tugmasi.{ kind: "loading" }—Yuklanmoqda…,textbo'lmasin.
Komponent ichida switch (props.kind) va never bilan to'liqlik tekshiruvi. App da error turini «Qayta urinish» bilan ko'rsating, bosilsa konsolga qayta…. Ishora: «Tuzoq: destrukturizatsiya union'ni buzadi» bo'limi — props ni butun holida tekshiring; never — React + TS II.
Yechim
type NoticeProps =
| { kind: "info"; text: string }
| { kind: "error"; text: string; retry: () => void }
| { kind: "loading" };
function Notice(props: NoticeProps) {
switch (props.kind) {
case "info":
return <p>{props.text}</p>;
case "error":
return (
<p role="alert">
{props.text}{" "}
<button onClick={props.retry}>Qayta urinish</button>
</p>
);
case "loading":
return <p>Yuklanmoqda…</p>;
default: {
const unhandled: never = props;
return unhandled;
}
}
}
export default function App() {
return (
<Notice
kind="error"
text="Menyu yuklanmadi"
retry={() => console.log("qayta…")}
/>
);
}Konsolda:
qayta…Ekranda:
Menyu yuklanmadi Qayta urinishcase "error" ichida props.retry — xatosiz, boshqa tarmoqlarda esa yo'q. <Notice kind="loading" text="…" /> — tsc xatosi, <Notice kind="error" text="…" /> (retry siz) — ham. Yangi kind qo'shilsa, never qatori case yozishni eslatadi.
4-mashq: Vazifalar qadami
Tugma.tsx ga Nomi union'ini qo'shing (Vazifalar qadami bo'limidagi parcha). Keyin o'lchang: src/sinov-tugma.tsx vaqtinchalik faylida uch tugma, npm run tip — xato faqat nomsizda. Faylni o'chiring.
Yechim
Tekshiruv ro'yxati:
npm run tipvaqtinchalik fayl bilan — bittaTS2322(Property '"aria-label"' is missing …),belgilivamatnli— xatosiz.- Fayl o'chirilgach — 0 xato. Mavjud 9 ta
<Tugma>o'zgarishsiz o'tdi. npm run lint,npm test— toza. Commit — keyingi darsda.
9. Real ishda
- Generic komponentlar — jadval (TanStack Table
useReactTable<Row>), ro'yxat,Select,Combobox— dizayn tizimlarining asosi. UlarTnidataprop'idan chiqaradi. - Union props —
Button(hrefbo'lsa<a>, bo'lmasa<button>),Alert(turiga qarab tugmalar), forma maydonlari (type="number"bo'lsamin/max). neverbilan istisno — "yovalue+onChange, yodefaultValue" (Controlled va uncontrolled API) qoidasini ham shunday yozish mumkin.- Polymorphic — Chakra/MUI'da
as/component, Radix'daasChild. Intervyuda "polymorphic komponentni qanday turlaysiz?" — senior darajadagi savol. satisfies— konfiguratsiya, marshrutlar ro'yxati, tarjima kalitlari (keyingi darsda).
Xulosa
- Generic komponent —
function List<T>(props: ListProps<T>);Tprops'dan chiqariladi, kerak bo'lsa<List<Dish> …>..tsxda strelka —<T,>. - Discriminated union props — "yo bu, yo u":
kindbo'yicha tsc to'g'ri variantni tanlaydi....restbilan destrukturizatsiya — bog'lanishni uzadi. - Ajratuvchisiz istisno —
?: never:{ children } | { children?: never; "aria-label" }. - Polymorphic
as—E extends ElementType+ComponentProps<E>; kuchli, lekin xabarlar uzun va tsc sekinroq. satisfies— tekshiradi, aniq turni saqlaydi.vazifalar:Tugma— nomi bo'lishi shart; nomsiz tugma —TS2322, 9 ta mavjud ishlatilish o'zgarishsiz o'tdi.
Keyingi dars: Kutubxona turlari va module augmentation — @types paketlari, declare module va kutubxonani "register" qilish.
Manbalar
- React: "Using TypeScript" — react.dev/learn/typescript (2026-10 holatiga, React 19.3)
- TypeScript: Generics, Narrowing (discriminated unions),
satisfies— typescriptlang.org/docs/handbook - TypeScript 4.6: "Control-flow analysis for destructured discriminated unions" — typescriptlang.org/docs/handbook/release-notes/typescript-4-6.html
- Radix UI: Composition —
asChild— radix-ui.com/primitives/docs/guides/composition
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!