7 β Union va literal tiplar¶
β¬ οΈ Oldingi: 06 β Funksiya tiplari Β· π README Β· Keyingi: 08 β Type narrowing va type guard'lar β‘οΈ
Bu bobda: ko'pincha bitta qiymat "bir nechta tipdan biri" yoki "aniq qiymatlardan biri" bo'ladi β kirish maydoni
stringyonumber, o'lcham faqatkichik,ortayokikatta, API javobi esayuklanmoqda/muvaffaqiyat/xato. Mana shu "yo... yo..." ni TypeScript'da union (|β birlashma tipi) va literal (aniq qiymat) tiplari ifodalaydi. Bu bobda union'da nima uchun faqat umumiy xossalar mavjudligini, literal union qanday qilibenum'ning o'rnini bosishini,null | Tqolipini va eng kuchli qurol β discriminated union (tag maydonli union)ni real kod bilan ko'rib chiqamiz.
Muammo¶
JavaScript'da bunday funksiya yozasiz:
function chegirma(narx, foiz) {
return narx - (narx * foiz) / 100;
}
chegirma(50000, "10"); // β "10" β satr! Natija: 45000, lekin xato yo'q
Bu yerda "10" satri narx * foiz ko'paytmasida jimgina 10 soniga o'tib ketadi, shuning uchun natija aldamchi β toza 45000 chiqadi, hech qanday ogohlantirishsiz. Bug aynan shu: kod "ishlayotgandek" tuyuladi. Endi kimdir "o'n" yoki "abc" bersa, ko'paytma NaN ga aylanadi va dastur allaqachon boshqa joyga ketib qolgan paytda buziladi. JavaScript indamaydi: foiz ga satr ham, son ham, hatto null ham bering β hammasini "yutadi". 6-bobda buni hal qildik: foiz: number deb yozsangiz, satr berishga yo'l qo'ymaydi.
Lekin haqiqiy hayotda tip ko'pincha bitta emas. Ikkita real holatni olaylik:
- Kirish maydoni. Foydalanuvchi ID'si β ba'zan son (
42), ba'zan satr ("ABC-42"). Ikkalasiga ham ruxsat berish kerak, lekinboolean'ga emas. - Cheklangan tanlov. Mahsulot o'lchami faqat
"kichik","orta"yoki"katta"bo'lishi mumkin.numberjuda keng β"o'rtachaa"(xato yozilgan) ni ham,"qizil"ni ham qabul qilmasligi kerak.
string yoki number bu cheklovlarni ifodalay olmaydi. Bizga kerak: "yo bu, yo u". Mana shu uchun union va literal tiplar bor.
Union tip β | belgisi¶
Union (birlashma) tipi ikki yoki undan ortiq tipni | (vertikal chiziq) bilan ulaydi. O'qilishi: "yo chapdagi, yo o'ngdagi tip".
let id: string | number;
id = 42; // β
number β mayli
id = "ABC-42"; // β
string β bu ham mayli
π | belgisini "yoki" deb o'qing. string | number β "satr yoki son". Belgisi mantiqiy "yoki" (||) ga o'xshaydi, lekin bu tip darajasida, qiymat darajasida emas.
Funksiya argumentida ham xuddi shunday:
function chop(qiymat: string | number): void {
console.log(qiymat);
}
chop(10); // β
chop("salom"); // β
chop(true); // β boolean union'da yo'q
Oxirgi qator kompilyatsiyadan o'tmaydi:
π‘ Union'ga nechta a'zo qo'shsangiz bo'ladi: string | number | boolean | null. Lekin amalda 2-4 a'zo eng tushunarli β ko'paysa, ehtimol tipni qaytadan o'ylash kerak.
Union'da faqat UMUMIY a'zolar mavjud¶
Eng muhim qoida shu yerda. x: string | number bo'lsa, TypeScript aniq bilmaydi β bu hozir satrmi yo son. Shuning uchun u faqat ikkala tipda ham bor xossalar va metodlarga ruxsat beradi.
function uzunlik(x: string | number): number {
// .toString() β string'da ham, number'da ham bor (umumiy)
return x.toString().length; // β
}
.toString() ishlaydi, chunki u har ikkala tipda mavjud. Endi faqat bitta tipga xos narsani ishlatib ko'ring:
function xato(x: string | number): number {
return x.length; // β length faqat string'da bor, number'da yo'q
}
Kompilyator to'xtatadi:
error TS2339: Property 'length' does not exist on type 'string | number'.
Property 'length' does not exist on type 'number'.
Mantiq oddiy: x son bo'lib qolsa-chi? Sonning .length'i yo'q. TypeScript "ehtimol son" bo'lgan holatni hisobga olib, oldindan ogohlantiradi.
π Bu xatolik emas, balki himoya. JavaScript'da (42).length jimgina undefined qaytaradi va xato keyinroq, butunlay boshqa joyda yuzaga keladi. TypeScript esa muammoni aynan manbada ko'rsatadi.
π‘ "Unda .length'ni qachon ishlataman?" β qiymat aniq satr ekanini TypeScript'ga isbotlab bergandan keyin. Buni narrowing (tipni toraytirish) deyiladi β keyingi bobning butun mavzusi. Hozircha bir ko'rinishini ko'rsatamiz:
function format(x: string | number): string {
if (typeof x === "number") {
return x.toFixed(2); // bu yerda TypeScript biladi: x β number
}
return x.trim(); // bu yerda esa x β string
}
typeof tekshiruvidan keyin TypeScript har bir tarmoqda aniq tipni "ochib beradi". Bu sehrning to'liq tafsilotlari β 08-bobda.
Literal tiplar β aniq qiymatning o'zi tip bo'ladi¶
Endi eng qiziq qismi. Odatda string "har qanday satr" degani. Lekin TypeScript'da aniq bitta satrning o'zini tip qilib belgilash mumkin:
let yon: "chap";
yon = "chap"; // β
β yagona ruxsat etilgan qiymat
yon = "ong"; // β β "ong" bu tipga to'g'ri kelmaydi
"chap" tipi faqat va faqat "chap" qiymatini qabul qiladi. Yolg'iz holda bu unchalik foydali emas. Lekin uni union bilan birlashtirsangiz β kuchli vosita paydo bo'ladi:
let yon: "chap" | "ong" | "yuqori" | "past";
yon = "chap"; // β
yon = "ong"; // β
yon = "yuqori"; // β
Endi yon'ga faqat shu to'rt qiymatdan birini berasiz, boshqasini emas:
π Bu shunchaki "go'zal cheklov" emas β bu xatolarni yozish paytida tutadigan mexanizm. Klaviaturadan "ortacha" deb adashib yozsangiz, dasturni ishga tushirishdan oldin xato chiqadi.
Literal union'ni type bilan nomlash¶
Har safar uzun ro'yxatni qayta yozmaslik uchun unga nom bering (type haqida 10-bobda batafsil, hozircha "tipga taxallus" deb tushuning):
type Olcham = "kichik" | "orta" | "katta";
function buyurtma(olcham: Olcham): string {
return `Tanlandi: ${olcham}`;
}
buyurtma("orta"); // β
buyurtma("ortacha"); // β
So'nggi qator xato:
π‘ IDE'ning yana bir sovg'asi: buyurtma( deb yozganingizda muharrir o'zi kichik, orta, katta ni ro'yxat qilib taklif qiladi. Eslab o'tirmaysiz β tanlaysiz.
Numeric va boolean literal¶
Literal faqat satrlarga xos emas. Sonlar va boolean ham bo'ladi:
type Yonalish = -1 | 0 | 1; // faqat shu uchta son
let qadam: Yonalish = 1;
qadam = -1; // β
qadam = 2; // β 2 ro'yxatda yo'q
type Rost = true; // faqat true
let har_doim: Rost = true;
Numeric literal union ko'pincha "darajalar" yoki "bosqichlar" uchun ishlatiladi: type Yulduz = 1 | 2 | 3 | 4 | 5.
Literal union β enum'ning o'rnini bosadi¶
Boshqa tillardan kelganlar "cheklangan tanlov" uchun enum izlaydi. TypeScript'da enum bor, lekin zamonaviy uslubda ko'pincha literal union afzal ko'riladi:
// enum o'rniga β toza literal union:
type Status = "active" | "inactive" | "banned";
const foydalanuvchi: { ism: string; status: Status } = {
ism: "Lola",
status: "active",
};
Nega union afzal? Chunki:
- Qo'shimcha kod yo'q.
enumJavaScript'ga aylanganda haqiqiy obyekt yaratadi; union esa kompilyatsiyadan keyin butunlay yo'qoladi (faqat tekshiruv uchun mavjud). - Qiymatlar oddiy satr.
statusaynan"active"satrining o'zi β JSON'ga yozish, API'ga yuborish oson.enumbilanStatus.Activedeb yozish kerak bo'lardi. - Solishtirruv tabiiy.
if (status === "active")β qo'shimcha import'siz ishlaydi.
π enum butunlay yomon degani emas β ba'zan o'rinli. Lekin 2026-yilda boshlovchiga maslahat: avval literal union'ni o'ylang, enum'ni faqat alohida ehtiyoj bo'lganda ishlating. Buni 04-bobda ham eslatgan edik.
π‘ as const bilan obyektdan ham union "tortib olish" mumkin (10-bob), lekin bevosita literal union β eng sodda boshlanish nuqtasi.
null | T β "qiymat bor yoki yo'q" qolipi¶
Funksiya ba'zan natija topadi, ba'zan topmaydi. JavaScript'da "topilmadi" ni null bilan bildirasiz. TypeScript'da buni tipda ochiq aytasiz: string | null.
Bu tip o'qiyotgan har bir dasturchiga aytadi: "natija satr yoki null β null holatini ko'rib chiqishni unutma". Va TypeScript buni majburlaydi:
Kompilyator haq: natija null bo'lsa, null.toUpperCase() JavaScript'da dasturni qulatadi (TypeError). TypeScript bu falokatni yozish paytida to'xtatadi. To'g'ri yo'l β avval tekshirish:
const natija = qidir(2);
if (natija !== null) {
console.log(natija.toUpperCase()); // β
bu yerda natija β string
}
π Bu strictNullChecks (strict rejimning bir qismi) tufayli ishlaydi. Strict yoqilmagan bo'lsa, null har qanday tipga "sirg'alib kiradi" va aynan shu β JavaScript'dagi mashhur "undefined is not a function" xatolarining ildizi. Strict rejimni doim yoqib turing.
π‘ T | undefined ham xuddi shu g'oya. Masalan, obyektdan kalit izlaganda:
function topNarx(mahsulot: string): number | undefined {
const narxlar: Record<string, number> = { non: 5000, sut: 12000 };
return narxlar[mahsulot]; // topilmasa β undefined
}
const n = topNarx("non");
console.log(n ?? "topilmadi"); // ?? β null/undefined uchun zaxira qiymat
?? (nullish coalescing) operatorini JavaScript kitobida ko'rgansiz; bu yerda u number | undefined ni xavfsiz ishlatishga yordam beradi.
Union'da MAXSUS xossa β to'g'ridan-to'g'ri bo'lmaydi¶
Endi obyektlardan tashkil topgan union'ga o'tamiz. "Mehmon" va "A'zo" foydalanuvchilarni tasavvur qiling:
type Mehmon = { ism: string };
type Azo = { ism: string; balli: number };
type Foydalanuvchi = Mehmon | Azo;
ism β ikkalasida ham bor (umumiy), shuning uchun unga bemalol murojaat qilasiz. Lekin balli faqat Azo'da bor:
function ball(f: Foydalanuvchi): number {
return f.balli; // β f mehmon bo'lsa-chi? Unda balli yo'q
}
error TS2339: Property 'balli' does not exist on type 'Foydalanuvchi'.
Property 'balli' does not exist on type 'Mehmon'.
Yana o'sha qoida: union faqat umumiy xossalarga ruxsat beradi. Maxsus xossaga yetish uchun avval qaysi a'zo ekanini aniqlashtirish kerak. Buning bir usuli β in operatori bilan tekshirish:
function ball(f: Mehmon | Azo): number {
if ("balli" in f) {
return f.balli; // β
bu yerda f β Azo
}
return 0; // mehmon: balli yo'q, 0 qaytaramiz
}
π "balli" in f β JavaScript'ning in operatori: obyektda shu kalit bormi. TypeScript buni "isbot" deb qabul qiladi va if ichida f ni Azo ga toraytiradi. Bu ham narrowing β 08-bobda chuqurroq.
Discriminated union β union'ning eng kuchli ko'rinishi¶
Mana bu naqsh sizning kundalik quroliingizga aylanadi. G'oya: har bir union a'zosiga bir xil nomli, har birida boshqacha literal qiymatli "tag" (yorliq) maydon qo'yamiz. Bu maydonni discriminant (ajratuvchi) deyiladi.
Shakllarni hisoblaylik:
type Doira = { shakl: "doira"; radius: number };
type Tortburchak = { shakl: "tortburchak"; eni: number; boyi: number };
type Shakl = Doira | Tortburchak;
shakl maydoni β tag: Doira'da u "doira", Tortburchak'da "tortburchak". Endi switch bilan tagni tekshirsangiz, TypeScript har tarmoqda aniq tipni biladi:
function yuza(s: Shakl): number {
switch (s.shakl) {
case "doira":
return Math.PI * s.radius ** 2; // s β Doira, radius bor
case "tortburchak":
return s.eni * s.boyi; // s β Tortburchak, eni/boyi bor
}
}
case "doira" ichida s.radius ga ruxsat bor, lekin s.eni ga yo'q β TypeScript tagga qarab to'g'ri a'zoni "ochadi". Bu eng tabiiy va xavfsiz union ishlatish usuli.
Real misol: API javobi¶
Discriminated union API holatlarini ifodalashda mukammal. Frontend'da so'rov uch holatdan birida bo'ladi:
type Javob =
| { holat: "yuklanmoqda" }
| { holat: "muvaffaqiyat"; malumot: string[] }
| { holat: "xato"; xabar: string };
function korsat(j: Javob): string {
switch (j.holat) {
case "yuklanmoqda":
return "Kuting...";
case "muvaffaqiyat":
return `${j.malumot.length} ta natija`; // malumot faqat shu yerda bor
case "xato":
return `Xato: ${j.xabar}`; // xabar faqat shu yerda bor
}
}
π E'tibor bering: j.malumot ga faqat "muvaffaqiyat" tarmog'ida, j.xabar ga faqat "xato" tarmog'ida murojaat qila olasiz. "Yuklanmoqda" holatida ma'lumotni o'qishga urinib bo'lmaydi β bu mantiqan to'g'ri, chunki hali ma'lumot kelmagan. Tip tizimi noto'g'ri kodni yozdirmaydi.
never bilan "hammasini qopladimmi?" kafolati¶
Discriminated union'ning yana bir zo'r xususiyati: yangi a'zo qo'shganda biror holatni unutsangiz, TypeScript eslatadi. Buni never (hech qachon ro'y bermaydigan tip β 09-bobda) yordamida amalga oshiriladi:
type Hodisa =
| { tur: "bosish"; x: number; y: number }
| { tur: "yozish"; matn: string };
function ishlov(h: Hodisa): string {
switch (h.tur) {
case "bosish":
return `(${h.x}, ${h.y})`;
case "yozish":
return h.matn;
default:
const _hech: never = h; // hamma holat qoplanganini kafolatlaydi
return _hech;
}
}
Bu hozir toza kompilyatsiyadan o'tadi. Endi Hodisa'ga yangi a'zo qo'shsangiz, lekin switch'ga mos case qo'shishni unutsangiz:
type Hodisa =
| { tur: "bosish"; x: number; y: number }
| { tur: "yozish"; matn: string }
| { tur: "siljish"; delta: number }; // yangi a'zo, lekin case yo'q!
default tarmog'i xato beradi:
Mantiq nozik, lekin chiroyli: agar hamma case qoplangan bo'lsa, default ga hech qachon yetib bo'lmaydi β h'ning tipi never (bo'sh) bo'lib qoladi va never'ni never'ga berish mayli. Lekin biror holat qoplanmagan bo'lsa, h'da o'sha qoplanmagan a'zo qoladi β uni never'ga berib bo'lmaydi, xato chiqadi. Shunday qilib TypeScript "esingdan chiqdi!" deb baqiradi.
π‘ Bu naqshni exhaustiveness check (to'liq qoplash tekshiruvi) deyiladi. Katta loyihalarda bebaho: yangi holat qo'shganingizda, uni qayerda ishlash kerakligini kompilyatorning o'zi ko'rsatib beradi.
Literal vs umumiy tip β const va let farqi¶
Oxirgi muhim nuqta β boshlovchilarni ko'p chalkashtiradi. TypeScript o'zgaruvchi tipini const va let uchun boshqacha taxmin qiladi:
const aniq = "katta"; // tip: "katta" (literal!)
let umumiy = "katta"; // tip: string (kengaytirilgan)
Nega farq? const o'zgarmaydi β qiymat har doim aynan "katta", shuning uchun tip ham aniq "katta". let esa keyin o'zgarishi mumkin (umumiy = "boshqa" ham mumkin), shuning uchun TypeScript tipni ehtiyot yuzasidan string gacha kengaytiradi (widening).
Bu literal union'ga uzatishda bilinadi:
type Olcham = "kichik" | "katta";
function f(o: Olcham): void {
console.log(o);
}
f(aniq); // β
aniq tipi "katta" β Olcham'ga to'g'ri keladi
f(umumiy); // β umumiy tipi string β juda keng!
π Bu eng ko'p uchraydigan "nega ishlamayapti?!" holatlaridan biri. Sabab: let bilan e'lon qilingan o'zgaruvchi string ga kengayadi va endi "kichik" | "katta" ga sig'maydi. Yechimlar: o'zgaruvchini const qiling, yoki tipini ochiq belgilang (let umumiy: Olcham = "katta"), yoki as const bilan literalligini "qotiring".
π‘ Qoida: o'zgartirmasangiz β const. Bu nafaqat xavfsizroq, balki TypeScript'ga aniqroq, "literal" tip beradi va kodingiz aqlliroq bo'ladi.
Union va literal tiplar bilan "yo bu, yo u" holatlarini aniq ifodalashni o'rgandingiz: string | number, "kichik" | "orta" | "katta", T | null, va eng kuchlisi β tag maydonli discriminated union. Bir narsa qayta-qayta takrorlandi: union'da maxsus xossaga yetish uchun avval qaysi a'zo ekanini aniqlashtirish kerak. Aynan shu β tipni toraytirish (narrowing) β keyingi bobning to'liq mavzusi: typeof, in, instanceof va o'zingizning type guard'laringiz.
7-bob mashqlari¶
Yechimlarni yozmaymiz β har birini o'zingiz
.tsfaylga yozib,tsc --noEmit --strictbilan tekshiring. "Xato chiqsin" deyilgan joylarda xato aynan qayerni ko'rsatayotganini o'qing.
qiymatnomli o'zgaruvchi e'lon qiling, tipistring | numberbo'lsin. Unga avval son, keyin satr bering β ikkalasi ham o'tishini tasdiqlang.- 1-mashqdagi o'zgaruvchiga
trueberib ko'ring. Qanday xato chiqadi? Xato matnini yozib qo'ying. Olchamnomli literal union yarating:"kichik" | "orta" | "katta". Unga"orta"ni, keyin"juda-katta"ni bering. Ikkinchisi qanday xato beradi?bahola(ball: number): "yiqildi" | "otdi" | "ajoyib"funksiyasini yozing:ball < 60bo'lsa"yiqildi",60-85oralig'ida"otdi", aks holda"ajoyib"qaytarsin.chiziq(yon: "chap" | "ong" | "yuqori" | "past"): voidfunksiyasini yozing. To'rttala to'g'ri qiymat bilan chaqiring, keyin xato qiymat bilan chaqirib, kompilyator taklif ro'yxatini ko'ring.chop(x: string | number): numberfunksiyasixuzunligini qaytarsin (.toString().length). Negax.lengthto'g'ridan-to'g'ri ishlamasligini izohlovchi sharh yozing.Yulduznomli numeric literal union yarating:1 | 2 | 3 | 4 | 5.baho: Yulduzo'zgaruvchisiga3bering, keyin6berib xatoni ko'ring.topUser(id: number): string | nullfunksiyasiid === 1bo'lganda ism, aks holdanullqaytarsin. Natijaninulltekshiruvisiz.toUpperCase()qilib, xatoni ko'ring; keyinifbilan to'g'irlang.narx(mahsulot: string): number | undefinedfunksiyasi obyektdan narx izlasin ({ non: 5000 }). Natijani??bilan"yo'q"ga zaxiralab chop eting.type Til = "uz" | "en" | "ru"yarating vasalom(til: Til): stringfunksiyasi har til uchun mos salomlashuvni qaytarsin.const aniq = "ha"valet umumiy = "ha"deb yozing. Ikkalasining tipini muharrirda ko'ring (sichqonchani ustiga olib boring). Farqini sharhda yozing.- 11-mashqdagi
umumiynitype Javob = "ha" | "yoq"tipidagi funksiyaga uzating. Xato chiqsa β uni 3 xil usulda to'g'irlang (const, ochiq tip,as const). Mehmon = { ism: string }vaAzo = { ism: string; obuna: boolean }tiplaridanFoydalanuvchiunion yarating.obunaga to'g'ridan-to'g'ri murojaat qilib xatoni ko'ring.- 13-mashqni
"obuna" in ftekshiruvi bilan to'g'irlang: a'zo bo'lsaobunani, mehmon bo'lsafalseni qaytaring. - Discriminated union yarating:
type Tolov = { usul: "naqd"; summa: number } | { usul: "karta"; raqam: string }.switch (t.usul)bilan har usulni alohida ishlovchi funksiya yozing. - 15-mashqqa
defaulttarmog'idaconst _x: never = tqo'shing. Toza o'tishini tasdiqlang. - 15-mashqdagi
Tolov'ga uchinchi a'zo qo'shing:{ usul: "uzatma"; iban: string }, lekinswitch'ga moscaseqo'shmang.nevertarmog'i qanday xato berishini yozib oling. type Javob = { holat: "yuklanmoqda" } | { holat: "tayyor"; data: number[] }yarating.tayyorholatidadata.lengthni chop etuvchi funksiya yozing.yuklanmoqdatarmog'idadataga murojaat qilib ko'ring β nima bo'ladi?format(x: string | number): stringfunksiyasini yozing:typeof x === "number"bo'lsax.toFixed(1), aks holdax.trim()qaytarsin. Har tarmoqda TypeScriptxni qaysi tip deb bilishini sharhda ko'rsating.- (Birlashtiruvchi mashq) Mini kalkulyator:
type Amal = "+" | "-" | "*" | "/".hisobla(a: number, b: number, amal: Amal): number | nullfunksiyasi mos amalni bajarsin;"/"dab === 0bo'lsanullqaytarsin. So'ng natijaninulltekshiruvi bilan xavfsiz chop eting.