Mundarija (29)
- Bu darsda
- 1. Nega bu kerak?
- 2. Conditional type: tur darajasidagi if
- 2.1 Yozuv
- 2.2 Zanjir: else if
- 3. Taqsimlanish: union ustida shart
- 3.1 Har a'zo alohida
- 3.2 Exclude va Extract ichidan
- 3.3 Taqsimlanish kutilmagan natija berganda
- 3.4 [T] — taqsimlanishni to'xtatish
- 4. Mapped type + conditional type: maydonlarni filtrlash
- 5. Generic funksiyada conditional natija turi
- 5.1 Kompilyator shartni "keyinga qoldiradi"
- 5.2 as — va TypeScript ushlamaydigan tuzoq
- 5.3 Generic tanada T[K] ham hisoblanmaydi
- 6. Conditional type qachon kerak
- 7. Ko'p uchraydigan xatolar
- 7.1 Union'da "butunligicha" tekshiruv kutish
- 7.2 Taqsimlanish massivni bo'lib yuboradi
- 7.3 Generic funksiya ichida shartni toraytirish kutish
- 7.4 extends yo'nalishini adashtirish
- 7.5 Juda uzun zanjir
- 8. Mashqlar
- 1-mashq (oson): Natijani hisoblang
- 2-mashq (o'rta): O'z Extract va StringKeys ingiz
- 3-mashq (qiyin): Amaliy tajriba — kunlik hisobot
- 9. Real ishda
- Xulosa
- Manbalar
TypeScript conditional types: tur darajasidagi if — T extends U ? X : Y
Qisqacha: Conditional type — turlar uchun
if:T extends U ? X : Y— "TturiUga mos kelsa —X, aks holda —Y".Tunion bo'lsa, shart har bir a'zoga alohida qo'llanadi (taqsimlanish) —ExcludevaExtractshu bilan ishlaydi. Taqsimlanishni to'xtatish uchun ikkala tomon kvadrat qavsga olinadi:[T] extends [U].
Bu darsda
A extends B ? X : Yni o'qiysiz va kompilyator uni qanday hisoblashini jadvalda ko'rsatasiz.- Union ustida taqsimlanishni (distributive) tushuntirasiz va
[T]bilan to'xtatasiz. Exclude,Extract,NonNullablening ichini o'qiy olasiz.- Mapped type va conditional type'ni birlashtirib, "faqat son maydonlar" kabi filtr yozasiz.
- Generic funksiyada conditional natija turi nega
astalab qilishini va bu yerda TypeScript nimani ushlamasligini bilasiz.
Oldin bilishingiz kerak: Mapped types, Utility types, Union va literal turlar.
1. Nega bu kerak?
Utility types darsida Exclude ni ishlatdik, o'tgan darsda esa mapped type ichida undan kalitlarni o'chirishda foydalandik. Lekin u qanday ishlashini ko'rmadik. Bugun ko'ramiz — va shu bilan TypeScript'ning eng kuchli vositalaridan biri ochiladi.
«Bahor» oshxonasida buyurtma beshta holatdan birida bo'ladi:
type OrderStatus =
| "yangi"
| "tayyorlanmoqda"
| "tayyor"
| "berildi"
| "bekor";
const status: OrderStatus = "tayyor";
console.log(status); // tayyorOshxona ekranida faqat yopilmagan buyurtmalar ko'rinadi: berilgan va bekor qilinganlari yo'q. Sardor ular uchun alohida tur yozdi:
type ActiveStatus = "yangi" | "tayyorlanmoqda" | "tayyor";
const shown: ActiveStatus = "yangi";
console.log(shown); // yangiBir oydan keyin Jasur aka yangi holat qo'shdi — "qadoqlanmoqda" (olib ketish uchun). Sardor uni OrderStatus ga qo'shdi, ActiveStatus ga esa unutdi. Natija: olib ketiladigan buyurtmalar oshxona ekranidan yo'qoldi. O'tgan darsdagi muammo — bitta ro'yxat ikki joyda yozilgan.
Yechim — ActiveStatus ni OrderStatus dan hisoblab chiqarish: "hamma holatlar, berilgan va bekor qilingandan tashqari".
type OrderStatus =
| "yangi"
| "tayyorlanmoqda"
| "tayyor"
| "berildi"
| "bekor";
type ActiveStatus = Exclude<OrderStatus, "berildi" | "bekor">;
const shown: ActiveStatus = "bekor";Kompilyator:
TS2322: Type '"bekor"' is not assignable to type 'ActiveStatus'.Tarjimasi: ""bekor" turini ActiveStatus turiga berib bo'lmaydi". Endi OrderStatus ga yangi holat qo'shilsa, u o'zi ActiveStatus ga ham tushadi. Exclude qanday qilib "olib tashlay" oladi? Uning ta'rifi lib.es5.d.ts da bitta qator:
type Exclude<T, U> = T extends U ? never : T;Bu — conditional type. Darsning qolgan qismida shu qatorni bo'lakma-bo'lak ochamiz.
2. Conditional type: tur darajasidagi if
2.1 Yozuv
Conditional type (shartli tur) — shartga qarab ikki turdan birini tanlaydigan tur. Yozuvi JavaScript'dagi ternary operator ga o'xshaydi:
// JavaScript: qiymatlar bilan
const label = price > 30000 ? "qimmat" : "arzon";
// TypeScript: turlar bilan
type Label<T> = T extends string ? "matn" : "boshqa";| Bo'lak | Ma'nosi |
|---|---|
T extends string |
shart: "T — string ga mosmi?" |
? "matn" |
ha bo'lsa — shu tur |
: "boshqa" |
yo'q bo'lsa — shu tur |
Bu yerda extends — "meros oladi" emas (klasslardagi kabi). U "mos keladimi" degani: T turidagi qiymatni string turidagi o'zgaruvchiga berib bo'ladimi? "osh" — ha, 35000 — yo'q. Bu generic cheklovdagi T extends … bilan bir xil ma'no.
Birinchi misol:
type IsString<T> = T extends string ? "ha" : "yo'q";
const a: IsString<"osh"> = "ha";
const b: IsString<35000> = "yo'q";
console.log(a, b); // ha yo'qKompilyator IsString<"osh"> ni shunday hisoblaydi:
| Qadam | Nima qiladi | Natija |
|---|---|---|
| 1 | T o'rniga "osh" qo'yadi |
"osh" extends string ? "ha" : "yo'q" |
| 2 | "osh" ni string ga berib bo'ladimi? |
ha |
| 3 | ? dan keyingi tur |
"ha" |
IsString<35000> da ikkinchi qadamda javob "yo'q" — natija "yo'q". Noto'g'ri qiymat berib ko'ramiz:
type IsString<T> = T extends string ? "ha" : "yo'q";
const b: IsString<35000> = "ha";Kompilyator:
TS2322: Type '"ha"' is not assignable to type '"yo'q"'.Tarjimasi: ""ha" turini "yo'q" turiga berib bo'lmaydi". Xabarda IsString<35000> emas, uning hisoblangan natijasi turibdi — "yo'q". Shart oddiy tur bilan bo'lsa, tsc uni darhol hisoblaydi.
2.2 Zanjir: else if
JavaScript'dagi if … else if … else kabi, conditional type'ni ham zanjir qilish mumkin. Oshxona ekranidagi yorliqlar:
type Label<S> = S extends "yangi"
? "Yangi"
: S extends "tayyor"
? "Tayyor"
: "Bekor qilindi";
const a: Label<"tayyor"> = "Yangi";Kompilyator:
TS2322: Type '"Yangi"' is not assignable to type '"Tayyor"'.Tarjimasi: ""Yangi" ni "Tayyor" ga berib bo'lmaydi" — tsc zanjirni hisoblab, Label<"tayyor"> = "Tayyor" ekanini topdi:
| Qadam | Shart | Javob | Keyingisi |
|---|---|---|---|
| 1 | "tayyor" extends "yangi" |
yo'q | : dan keyingi qism |
| 2 | "tayyor" extends "tayyor" |
ha | "Tayyor" |
Zanjir yuqoridan pastga tekshiriladi, birinchi "ha" — natija. Uch-to'rt pog'onadan uzun zanjirni o'qish qiyinlashadi — unda mapped type yoki Record dan foydalangan ma'qul (masalan, Record<OrderStatus, string> — yorliqlar jadvali).
Tekshirib ko'ring:
type T1 = 35000 extends number ? "son" : "boshqa";—T1nima?type T2 = number extends 35000 ? "ha" : "yo'q";chi?
Javob
T1 — "son": aniq son 35000 har qanday number o'zgaruvchiga beriladi. T2 — "yo'q": hamma sonlarni (number) faqat 35000 qabul qiladigan joyga berib bo'lmaydi, masalan 28000 ham son. "Mos keladimi" yo'nalishi muhim: tor tur kengiga mos keladi, teskarisi emas.
3. Taqsimlanish: union ustida shart
3.1 Har a'zo alohida
Endi qiziq joy. IsString ga union bersak nima bo'ladi?
type IsString<T> = T extends string ? "ha" : "yo'q";
type Mixed = IsString<"osh" | 35000>;
const m1: Mixed = "ha";
const m2: Mixed = "yo'q";
console.log(m1, m2); // ha yo'qHover: type Mixed = "ha" | "yo'q". Kutilgan javob "yo'q" edi (union butunlay satr emas), lekin natija — ikkalasi. Sababi: shartdagi tur — yalang'och tur parametri (T ning o'zi, hech narsaga o'ralmagan) bo'lsa, union'ning har a'zosi alohida tekshiriladi va natijalar yana union'ga yig'iladi. Bu taqsimlanish (distributive conditional type) deyiladi.
| Qadam | Nima qiladi | Natija |
|---|---|---|
| 1 | union'ni a'zolarga ajratadi | "osh", 35000 |
| 2 | IsString<"osh"> |
"ha" |
| 3 | IsString<35000> |
"yo'q" |
| 4 | natijalarni union'ga yig'adi | "ha" | "yo'q" |
Xuddi shu jarayon rasmda:
flowchart TD
A["IsString: osh | 35000"] --> B["osh extends string?"]
A --> C["35000 extends string?"]
B -- "ha" --> D["natija: ha"]
C -- "yo'q" --> E["natija: yo'q"]
D --> F["yig'indi: ha | yo'q"]
E --> FKunlik o'xshatish: pochtachi bir quti xatni ko'chaga olib chiqadi. U qutini "bu ko'cha uchunmi?" deb butunligicha tekshirmaydi — har xatni alohida oladi va har birini o'z eshigiga eltadi.
Nega TypeScript shunday qiladi? Chunki union bilan ishlaganda deyarli har doim aynan shu kerak. "Holatlardan berilganlarini olib tashla", "taomlardan faqat ichimliklarni qoldir", "turdan null ni chiqar" — hammasi a'zo-a'zo ishlaydigan amallar. Taqsimlanish bo'lmaganda Exclude ni bitta qator bilan yozib bo'lmasdi. Ammo bu qoida jim ishlaydi: kodda hech qanday belgi yo'q. Shuning uchun uni yodda tutish kerak — keyingi bo'limlarda u kutilmagan natija berganini ham ko'ramiz.
3.2 Exclude va Extract ichidan
Endi Exclude ni o'qiy olamiz. T extends U ? never : T — "a'zo U ga mos kelsa — never (hech narsa), aks holda — a'zoning o'zi". never union'da "bo'sh joy" — "a" | never — shunchaki "a".
Exclude<OrderStatus, "berildi" | "bekor">:
| A'zo | U ga mosmi? |
Natija |
|---|---|---|
"yangi" |
yo'q | "yangi" |
"tayyorlanmoqda" |
yo'q | "tayyorlanmoqda" |
"tayyor" |
yo'q | "tayyor" |
"berildi" |
ha | never |
"bekor" |
ha | never |
Yig'indi: "yangi" | "tayyorlanmoqda" | "tayyor". never lar g'oyib bo'ldi.
Extract — teskarisi: T extends U ? T : never — "faqat U ga moslarini qoldir". Ikkinchi ro'yxatda T da yo'q narsa bo'lsa, u shunchaki e'tiborsiz qoladi:
type OrderStatus = "yangi" | "tayyor" | "berildi" | "bekor";
type Closed = Extract<OrderStatus, "berildi" | "bekor" | "yoqolgan">;
const c: Closed = "berildi";
console.log(c); // berildiHover: type Closed = "berildi" | "bekor". "yoqolgan" — OrderStatus da yo'q, u natijaga kirmadi.
NonNullable ham tarixda xuddi shunday yozilgan edi: T extends null | undefined ? never : T. Hozirgi lib.es5.d.ts da u qisqaroq — T & {} (kesishma): {} "null va undefined dan boshqa har narsa" degani. Natija bir xil.
3.3 Taqsimlanish kutilmagan natija berganda
Taqsimlanish ba'zan xalaqit beradi. Taomlar ro'yxati turini yasaymiz:
type ListOf<T> = T extends string ? T[] : never;
type Dishes = ListOf<"osh" | "manti">;
const order: Dishes = ["osh", "manti"];Kompilyator:
TS2322: Type '("manti" | "osh")[]' is not assignable to type 'Dishes'.
Type '("manti" | "osh")[]' is not assignable to type '"manti"[]'.
Type '"manti" | "osh"' is not assignable to type '"manti"'.
Type '"osh"' is not assignable to type '"manti"'.Kutganimiz — "osh yoki manti'lar massivi": ("osh" | "manti")[]. Taqsimlanish esa har a'zoni alohida o'radi: "osh"[] | "manti"[] — "yo faqat oshlar massivi, yo faqat mantilar massivi". Aralash buyurtma ikkalasiga ham mos emas. Zanjirni pastdan o'qing: ""osh" ni "manti" ga berib bo'lmaydi" — tsc "manti"[] variantini sinab ko'rgan.
(Xabarda union a'zolari "manti" | "osh" tartibida — TypeScript ularni o'zining ichki tartibida chiqaradi, siz yozgan tartibda emas. Ma'nosi bir xil.)
3.4 [T] — taqsimlanishni to'xtatish
Taqsimlanish faqat yalang'och T da ishlaydi. T ni biror narsaga o'rasak — masalan, bir elementli tuple'ga — u to'xtaydi:
type ListOf<T> = [T] extends [string] ? T[] : never;
type Dishes = ListOf<"osh" | "manti">;
const order: Dishes = ["osh", "manti", "osh"];
console.log(order.length); // 3Hover: type Dishes = ("osh" | "manti")[]. Kompilyator endi union'ni bo'lmaydi:
| Qadam | Nima qiladi | Natija |
|---|---|---|
| 1 | [T] ichida T yalang'och emas — union butunligicha qoladi |
["osh" | "manti"] extends [string] |
| 2 | bir elementli tuple'lar mosmi? | ha |
| 3 | T[] — butun union massivi |
("osh" | "manti")[] |
IsString da ham shunday: [T] extends [string] bilan IsString<"osh" | 35000> — "yo'q", chunki union butunligicha satr emas. Kvadrat qavs bu yerda maxsus sintaksis emas — oddiy tuple. Taqsimlanish qoidasi uni "yalang'och emas" deb biladi, xolos.
Yana ikki nozik holat:
- Shartda tur parametri emas, aniq tur bo'lsa (
string | number extends string ? …) — taqsimlanish yo'q: u faqat genericTuchun. never— "bo'sh union". Taqsimlanganda unda a'zo yo'q, shuning uchunIsString<never>—never(na"ha", na"yo'q").[T] extends [string]bilan esa —"ha":neveristalgan turga mos.
Tekshirib ko'ring:
Exclude<"osh" | "manti" | "somsa", "somsa" | "norin">nima beradi?
Javob
"osh" | "manti". Har a'zo alohida tekshiriladi: "osh" va "manti" ikkinchi ro'yxatda yo'q — qoladi, "somsa" bor — never bo'ladi. "norin" — birinchi ro'yxatda yo'q, u hech qanday ta'sir qilmaydi.
4. Mapped type + conditional type: maydonlarni filtrlash
«Bahor» kunlik hisobotida faqat son maydonlarni yig'ish mumkin: summa, stol raqami, kaloriya. Nom yoki holatni qo'shib bo'lmaydi. Qaysi kalitlar son ekanini turdan hisoblaymiz:
type MenuItem = {
name: string;
price: number;
calories: number;
available: boolean;
};
type NumberKeys<T> = {
[K in keyof T]: T[K] extends number ? K : never;
}[keyof T];
const key: NumberKeys<MenuItem> = "calories";
console.log(key); // caloriesBu yozuv uch bosqichda ishlaydi. Sekin ko'ramiz.
1-bosqich — mapped type. Har kalit uchun shart: qiymati son bo'lsa — kalitning nomi, aks holda never.
K |
MenuItem[K] |
… extends number |
Maydon |
|---|---|---|---|
"name" |
string |
yo'q | name: never |
"price" |
number |
ha | price: "price" |
"calories" |
number |
ha | calories: "calories" |
"available" |
boolean |
yo'q | available: never |
2-bosqich — [keyof T]. Yasalgan obyekt turidan hamma qiymatlarni olamiz (indexed access): never | "price" | "calories" | never.
3-bosqich — never lar o'chadi. Natija: "price" | "calories".
Kalitlar emas, maydonlarning o'zi kerak bo'lsa — o'tgan darsdagi as bilan bitta qadamda:
type MenuItem = { name: string; price: number; calories: number };
type NumberFields<T> = {
[K in keyof T as T[K] extends number ? K : never]: T[K];
};
const totals: NumberFields<MenuItem> = {
price: 63000,
calories: 1400,
};
console.log(totals.price); // 63000Hover: { price: number; calories: number }. Kalit never ga aylansa — o'chadi; shart buni hal qildi.
Ikki yozuvning farqi foydalanishda. NumberKeys — kalitlar union'i: u funksiya parametri yoki ro'yxat elementi bo'ladi ("qaysi ustun bo'yicha yig'amiz?"). NumberFields — obyekt turi: u natija shakli bo'ladi ("hisobotda qaysi maydonlar chiqadi?"). Ikkalasi ham MenuItem ga bog'langan: unga weight: number qo'shsangiz, ikkalasi ham o'zi kengayadi.
Tekshirib ko'ring:
MenuItemgaspicy: booleanqo'shilsa,NumberKeys<MenuItem>o'zgaradimi?
Javob
O'zgarmaydi. Mapped type yangi spicy kaliti uchun ham shartni tekshiradi: boolean extends number — yo'q, demak spicy: never. [keyof T] dan keyin never o'chib ketadi. Natija avvalgidek "price" | "calories".
5. Generic funksiyada conditional natija turi
5.1 Kompilyator shartni "keyinga qoldiradi"
Forma maydoni ikki xil: son (narx) va matn (nom). Sardor bitta funksiya yozmoqchi: turi "number" bo'lsa — son qaytarsin, "text" bo'lsa — satr. Natija turi kirishga bog'liq — conditional type'ning tabiiy joyi:
type Parsed<T> = T extends "number" ? number : string;
function parseField<T extends "number" | "text">(
kind: T,
raw: string,
): Parsed<T> {
if (kind === "number") {
return Number(raw);
}
return raw;
}Kompilyator:
TS2322: Type 'number' is not assignable to type 'Parsed<T>'.
TS2322: Type 'string' is not assignable to type 'Parsed<T>'.Tarjimasi: "number ni Parsed<T> ga berib bo'lmaydi" va "string ni Parsed<T> ga berib bo'lmaydi". Kod to'g'ri ko'rinadi, lekin ikkala return ham xato. Nega? Funksiya ichida T hali noma'lum — u chaqiruvda aniqlanadi. Kompilyatorning ikki joydagi hisobini solishtiring:
| Joy | T |
Parsed<T> |
|---|---|---|
| Funksiya ichida | noma'lum ("number" | "text" dan biri) |
hisoblanmaydi — "ochilmagan quti" |
parseField("number", …) chaqiruvida |
"number" |
number |
parseField("text", …) chaqiruvida |
"text" |
string |
Ichkarida Parsed<T> hisoblanmaydi: kompilyator shartni "keyinga qoldiradi" (deferred conditional type). kind === "number" tekshiruvi kind qiymatini toraytiradi (narrowing), lekin T turini toraytirmaydi. TypeScript 7.0.2 da ham shunday. Chaqiruvda esa — jadvalning pastki qatorlari — hammasi aniq.
5.2 as — va TypeScript ushlamaydigan tuzoq
Ko'p uchraydigan "yechim" — natijani as bilan majburlash (Type assertion). Mana shu yerda xato yashirinadi. Sardor shoshilib shartni teskari yozdi:
type Parsed<T> = T extends "number" ? number : string;
function parseField<T extends "number" | "text">(
kind: T,
raw: string,
): Parsed<T> {
return (kind === "number" ? raw : Number(raw)) as Parsed<T>;
}
const price = parseField("number", "35000");
console.log(price.toFixed(0));tsc — xatosiz. Node esa:
TypeError: price.toFixed is not a functionHover'da const price: number — chaqiruv tomoni to'g'ri tur oldi. Lekin funksiya ichida as Parsed<T> kompilyatorga "menga ishon" dedi va u tekshirishni to'xtatdi. Haqiqatda price — "35000" satri.
Bu darsning eng muhim ogohlantirishi shu. Conditional type — faqat kompilyatsiya paytidagi hisob. Ishlash paytida Parsed<T> ham, T ham yo'q: Node turlarni o'chirib, oddiy return raw ni bajaradi. Tur va haqiqiy qiymat orasidagi bog'lanishni faqat siz — if ichidagi kodingiz — ta'minlaysiz. as esa bu bog'lanishni tekshirishni o'chirib qo'yadi.
Qoida: conditional natija turi + as — tekshiruvsiz joy. Uni kichik qiling va testlang. Ko'p hollarda boshqa vosita yaxshiroq:
- Overload (Function overload):
function parseField(kind: "number", raw: string): number;vafunction parseField(kind: "text", raw: string): string;— chaqiruvchi uchun aniq turlar, ichkarida oddiynumber | string. - Ikki alohida funksiya:
parsePricevaparseName— eng sodda va eng xavfsiz.
5.3 Generic tanada T[K] ham hisoblanmaydi
Xuddi shu sabab bilan NumberKeys ni funksiya imzosida ishlatsak:
type NumberKeys<T> = {
[K in keyof T]: T[K] extends number ? K : never;
}[keyof T];
function sumField<T, K extends NumberKeys<T>>(
rows: readonly T[],
key: K,
): number {
let sum = 0;
for (const row of rows) sum += row[key];
return sum;
}Kompilyator:
TS2365: Operator '+=' cannot be applied to types 'number' and 'T[K]'."+= operatorini number va T[K] turlariga qo'llab bo'lmaydi". Biz bilamizki K — son maydon. Kompilyator esa T noma'lum bo'lgani uchun NumberKeys<T> ni hisoblamaydi va row[key] son ekanini bilmaydi. Amaliy yo'l — talabni teskari yozish: "qatorlar key kalitida son saqlaydigan obyektlar bo'lsin":
type MenuItem = { name: string; price: number; calories: number };
function sumField<K extends PropertyKey>(
rows: readonly Record<K, number>[],
key: K,
): number {
let sum = 0;
for (const row of rows) sum += row[key];
return sum;
}
const menu: MenuItem[] = [
{ name: "Osh", price: 35000, calories: 800 },
{ name: "Manti", price: 30000, calories: 600 },
];
console.log(sumField(menu, "price")); // 65000PropertyKey — string | number | symbol ning tayyor nomi. Record<K, number> da conditional yo'q — kompilyator row[key] ni darhol number deb biladi. MenuItem esa strukturaviy mos keladi: unda price: number bor. Nom maydonini bersak:
type MenuItem = { name: string; price: number };
function sumField<K extends PropertyKey>(
rows: readonly Record<K, number>[],
key: K,
): number {
let sum = 0;
for (const row of rows) sum += row[key];
return sum;
}
const menu: MenuItem[] = [{ name: "Osh", price: 35000 }];
sumField(menu, "name");Kompilyator:
TS2345: Argument of type 'MenuItem[]' is not assignable to parameter of type 'readonly Record<"name", number>[]'.
Type 'MenuItem' is not assignable to type 'Record<"name", number>'.
Types of property 'name' are incompatible.
Type 'string' is not assignable to type 'number'.Zanjirning oxirgi qatori — aniq sabab: "name — satr, son emas".
6. Conditional type qachon kerak
Ko'rganingizdek, conditional type kuchli, lekin har qadamda o'ylashni talab qiladi: taqsimlanish, never, keyinga qoldirilish. Shuning uchun uni ishlatishdan oldin o'zingizga ikki savol bering.
Birinchi savol: tur boshqa turdan hisoblanadimi? ActiveStatus — OrderStatus dan, NumberKeys<MenuItem> — MenuItem dan. Bu conditional type'ning eng foydali joyi: bitta manba o'zgarsa, hosilalar o'zi yangilanadi. Bunday holatda ko'pincha tayyor Exclude va Extract yetadi — o'zingiznikini yozish shart emas.
Ikkinchi savol: natija turi kirishga bog'liqmi? Bu holatda (parseField) avval overload'ni yoki ikki alohida funksiyani o'ylang. Conditional natija turi funksiya ichida as ni talab qiladi, as esa — tekshiruvsiz joy.
Qolgan holatlarda — oddiy union va obyekt turlari. Ilova kodida conditional type kam uchraydi. Kutubxona kodida esa juda ko'p — va endi siz uni o'qiy olasiz.
7. Ko'p uchraydigan xatolar
7.1 Union'da "butunligicha" tekshiruv kutish
IsString<"osh" | 35000> — "ha" | "yo'q", "yo'q" emas. Tuzatish: butun union kerak bo'lsa — [T] extends [string].
7.2 Taqsimlanish massivni bo'lib yuboradi
type ListOf<T> = T extends string ? T[] : never;
const order: ListOf<"osh" | "manti"> = ["osh", "manti"];Kompilyator:
TS2322: Type '("manti" | "osh")[]' is not assignable to type '"manti"[] | "osh"[]'.
Type '("manti" | "osh")[]' is not assignable to type '"manti"[]'.
Type '"manti" | "osh"' is not assignable to type '"manti"'.
Type '"osh"' is not assignable to type '"manti"'.Bu yerda tur nomi (Dishes) yo'q — tsc hisoblangan natijani ko'rsatdi: "manti"[] | "osh"[]. Taqsimlanish izi aniq ko'rinib turibdi. Tuzatish: [T] extends [string].
7.3 Generic funksiya ichida shartni toraytirish kutish
TS2322: Type 'number' is not assignable to type 'Parsed<T>'. — if T ni toraytirmaydi. Tuzatish: overload yoki ikki funksiya; as faqat ongli ravishda va testlar bilan.
7.4 extends yo'nalishini adashtirish
number extends 35000 — "yo'q", 35000 extends number — "ha". Qoida: chap tomon — tekshirilayotgan tur, o'ng tomon — "qolip". Tor tur kengiga mos keladi. Adashsangiz, tsc xato bermaydi — shunchaki boshqa shoxni tanlaydi. Shuning uchun yangi conditional type yozgach, uni ikki-uch aniq tur bilan hover'da tekshirib ko'ring: "ha" va "yo'q" shoxlari kutganingizdek ishlayaptimi.
7.5 Juda uzun zanjir
Besh-olti pog'onali A extends … ? … : B extends … ? … — o'qib bo'lmaydi va har qo'shilgan holatda buziladi. Tuzatish: Record<Kalit, Qiymat> jadvali va indexed access: Labels[S].
8. Mashqlar
1-mashq (oson): Natijani hisoblang
type IsNumber<T> = T extends number ? "son" : "boshqa";
type Wrap<T> = [T] extends [number] ? "son" : "boshqa";
const x: IsNumber<35000 | "osh"> = "boshqa";
const y: Wrap<35000 | "osh"> = "boshqa";
console.log(x, y); // boshqa boshqaIsNumber<35000> — [:son]. Wrap<35000 | "osh"> — [:boshqa]. IsNumber<35000 | "osh"> da nechta a'zo bor?
Yechim
IsNumber<35000>: 35000 — son, natija "son". Wrap da T kvadrat qavsda — taqsimlanish yo'q, 35000 | "osh" butunligicha son emas — "boshqa". IsNumber<35000 | "osh"> esa taqsimlanadi: "son" | "boshqa" — ikki a'zo. Shuning uchun x ga "boshqa" ham, "son" ham berish mumkin.
2-mashq (o'rta): O'z Extract va StringKeys ingiz
MyExtract<T, U>—Extractni o'zingiz yozing.StringKeys<T>— qiymati satr bo'lgan kalitlar. Ishora: «Mapped type + conditional type» bo'limidagiNumberKeysning bitta so'zini almashtiring.
Yechim
type MyExtract<T, U> = T extends U ? T : never;
type StringKeys<T> = {
[K in keyof T]: T[K] extends string ? K : never;
}[keyof T];
type OrderStatus = "yangi" | "tayyor" | "berildi" | "bekor";
type MenuItem = { name: string; note: string; price: number };
const closed: MyExtract<OrderStatus, "berildi" | "bekor"> = "bekor";
const textKey: StringKeys<MenuItem> = "note";
console.log(closed, textKey); // bekor noteMyExtract — lib.es5.d.ts dagi Extract bilan harfma-harf bir xil. StringKeys<MenuItem> — "name" | "note": price son, u never bo'lib o'chdi. Bunday tur qidiruv maydoni uchun foydali: "qaysi maydonlar bo'yicha matn qidirsa bo'ladi".
3-mashq (qiyin): Amaliy tajriba — kunlik hisobot
kurs/mashqlar/15/28-conditional/report.ts faylini yarating:
OrderStatus— besh holat;ActiveStatus—Excludebilan (berilgan va bekor qilinganlarsiz).NumberKeys<T>— darsdagidek.Order—id,table,total(sonlar) vastatus.isActive(status)— tur predikati (Custom type guard):status is ActiveStatus.sumField(rows, key)— tanlangan son maydon yig'indisi.SUMMABLE— hisobotda yig'iladigan maydonlar ro'yxati, turireadonly NumberKeys<Order>[], qiymati["total"].
Avval sumField ni darsdagi birinchi shaklda yozing — <T, K extends NumberKeys<T>>(rows: readonly T[], key: K) — va SUMMABLE ga ataylab "status" ni qo'shing. npx tsc --pretty false ni ishga tushiring. Keyin tuzating va report.test.ts da testlar yozing: faol buyurtmalarni ajratish, jami summa (bo'sh ro'yxat ham), boshqa son maydon yig'indisi.
Yechim
Birinchi variantda (testlar ham yozilgan bo'lsa) tsc uchta xato berdi:
28-conditional/report.test.ts(24,29): error TS2345: Argument of type '"total"' is not assignable to parameter of type 'never'.
28-conditional/report.ts(36,5): error TS2365: Operator '+=' cannot be applied to types 'number' and 'T[K]'.
28-conditional/report.ts(41,65): error TS2322: Type '"status"' is not assignable to type 'NumberKeys<Order>'.- Ikkinchisi — «Generic tanada
T[K]ham hisoblanmaydi» bo'limidagi xato. - Uchinchisi —
NumberKeysishladi:"status"son maydon emas. - Birinchisi — kutilmagan va qiziq:
sumField([], "total"). Bo'sh massivdanTni chiqarib bo'lmaydi, kompilyatorT = neverdeb oladi.NumberKeys<never>esanever— hech qanday kalit mos emas. Conditional va generic'lar birga ishlaganda shunday kutilmagan natijalar chiqadi.Record<K, number>shaklida bu muammo yo'q:Kto'g'ridan-to'g'ri"total"dan chiqariladi.
// 28-conditional/report.ts — «Bahor» kunlik hisoboti
export type OrderStatus =
| "yangi"
| "tayyorlanmoqda"
| "tayyor"
| "berildi"
| "bekor";
// Oshxona ekranida ko'rinadiganlar — yopilmagan buyurtmalar
export type ActiveStatus = Exclude<OrderStatus, "berildi" | "bekor">;
// T ning qiymati son bo'lgan kalitlari
export type NumberKeys<T> = {
[K in keyof T]: T[K] extends number ? K : never;
}[keyof T];
export type Order = {
id: number;
table: number;
total: number;
status: OrderStatus;
};
export function isActive(
status: OrderStatus,
): status is ActiveStatus {
return status !== "berildi" && status !== "bekor";
}
export function sumField<K extends PropertyKey>(
rows: readonly Record<K, number>[],
key: K,
): number {
let sum = 0;
for (const row of rows) {
sum += row[key];
}
return sum;
}
export const SUMMABLE: readonly NumberKeys<Order>[] = ["total"];// 28-conditional/report.test.ts
import { test } from "node:test";
import assert from "node:assert/strict";
import { isActive, sumField } from "./report.ts";
import type { Order } from "./report.ts";
const orders: Order[] = [
{ id: 1, table: 3, total: 70000, status: "berildi" },
{ id: 2, table: 5, total: 35000, status: "tayyorlanmoqda" },
{ id: 3, table: 1, total: 28000, status: "bekor" },
{ id: 4, table: 5, total: 40000, status: "yangi" },
];
test("isActive: faqat yopilmagan buyurtmalar", () => {
const active = orders.filter((o) => isActive(o.status));
assert.deepEqual(
active.map((o) => o.id),
[2, 4],
);
});
test("sumField: jami summa va bo'sh ro'yxat", () => {
assert.equal(sumField(orders, "total"), 173000);
assert.equal(sumField([], "total"), 0);
});
test("sumField: istalgan son maydon", () => {
assert.equal(sumField(orders, "table"), 14);
});Tuzatilgandan keyin npx tsc --pretty false — chiqishsiz. node --test 28-conditional/report.test.ts bizda (Node 24.21.0) shunday chiqdi:
✔ isActive: faqat yopilmagan buyurtmalar (3.0489ms)
✔ sumField: jami summa va bo'sh ro'yxat (0.3876ms)
✔ sumField: istalgan son maydon (0.324ms)
ℹ tests 3
ℹ suites 0
ℹ pass 3
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 286.3536OrderStatus ga "qadoqlanmoqda" ni qo'shib ko'ring: ActiveStatus uni o'zi oladi, isActive esa uni "faol" deb hisoblaydi — chunki u faqat yopilgan ikki holatni rad etadi. Shuning uchun tekshiruv "nima yopiq" ro'yxati bilan yozildi.
XATOLAR.md ga qo'shing: TS2365 (generic tanadagi T[K]), TS2322 ning "Parsed<T>" ko'rinishi (keyinga qoldirilgan conditional type). Keyin kurs/mashqlar papkasidan:
git add 15/28-conditional 15/XATOLAR.md
git commit -m "15/28: report.ts — Exclude, NumberKeys va sumField"9. Real ishda
- Kutubxona turlarining ichi.
Exclude,Extract,ReturnType,Parameters,Awaited— hammasi conditional type. Oxirgi uchtasiinferso'zini ishlatadi — uni keyingi darsda o'rganamiz. - Kutubxona muallifi darajasi. Murakkab conditional type'lar asosan kutubxonalar ichida yashaydi: Zod (keyinroq ko'ramiz) sxemadan tur chiqaradi, forma kutubxonalari maydon turiga qarab xato turini tanlaydi. Ilova kodida sizga ko'pincha
Exclude/Extractva bitta-ikkita oddiy shart yetadi. - Kalit filtrlari (
NumberKeys,StringKeys) — jadval komponentlari va hisobot generatorlarida tez-tez uchraydi: "qaysi ustun bo'yicha saralash/yig'ish mumkin". - Intervyu: "Distributive conditional type nima?", "
Excludeni o'zingiz yozing", "[T] extends [U]nega kerak?" — o'rta va yuqori darajadagi TypeScript intervyularida.
Xulosa
T extends U ? X : Y— tur darajasidagiif.extends— "mos keladimi" (assignable), "meros" emas.- Yalang'och
Tunion bo'lsa — shart har a'zoga alohida qo'llanadi va natijalar union'ga yig'iladi (taqsimlanish).never— union'dagi "bo'sh joy", o'chib ketadi. Exclude<T, U>=T extends U ? never : T;Extract— teskarisi.[T] extends [U]taqsimlanishni to'xtatadi.- Mapped type + conditional → kalitlarni qiymat turi bo'yicha filtrlash (
NumberKeys). - Generic funksiya ichida conditional tur hisoblanmaydi ("keyinga qoldiriladi");
asbilan majburlash — tekshiruvsiz joy. Overload yoki oddiyroq imzo afzal.
Keyingi dars: infer kalit so'zi — conditional type ichida turning bir qismini "tutib olib", nom berish: ReturnType ni o'zimiz yozamiz.
Manbalar
- TypeScript Handbook: "Conditional Types" (Distributive Conditional Types) — typescriptlang.org/docs/handbook/2/conditional-types.html
- TypeScript Release Notes 2.8 (conditional types,
Exclude,Extract,NonNullable) — typescriptlang.org/docs/handbook/release-notes/typescript-2-8.html lib.es5.d.ts— TypeScript bilan keladigan fayl (Exclude,Extract,NonNullable)
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!