15 β Mapped va conditional types¶
β¬ οΈ Oldingi: 14 β Utility types Β· π README Β· Keyingi: 16 β Template literal types β‘οΈ
Bu bobda: o'tgan bobda tayyor utility tiplardan (
Partial,Readonly,Pick,Record...) foydalandik. Endi ularning ich tomonini ochamiz va o'zimiz yasashni o'rganamiz. Mapped type ({ [K in keyof T]: ... }) bilan tip maydonlari ustidan iteratsiya qilishni,readonly/?modifikatorlarini qo'shish va olib tashlash (-readonly,-?),asbilan kalitlarni qayta nomlashni (key remapping); conditional type (T extends U ? X : Y) bilan tip darajasida "if" yozishni,inferorqali tip ichidan bo'lakni ajratib olishni (ReturnTypeshunday yasalgan), union ustidan tarqaladigan distributive xulq-atvorni vaDeepReadonly,Nullablekabi real tiplarni qurishni ko'rib chiqamiz.
Muammo¶
Tasavvur qiling, formangiz bor. Foydalanuvchini tahrirlash sahifasi: ism, email, telefon. Saqlashda butun obyekt kerak:
Lekin tahrirlash payti foydalanuvchi faqat bitta maydonni o'zgartirishi mumkin β masalan, faqat ismni. Demak forma holatida maydonlarning hammasi majburiy emas, "ixtiyoriy" bo'lishi kerak:
Bu ishlaydi, ammo o'ylab ko'ring: Foydalanuvchiga ertaga telefon maydoni qo'shsangiz, FoydaTahrirni ham qo'lda yangilashingiz kerak. Ikki joyni sinxron tutish β unutiladigan, xato chiqaradigan ish. 14-bobda buni Partial<Foydalanuvchi> bir qatorda hal qilishini ko'rdik. Lekin Partialning o'zi qanday ishlaydi? Sehr emas β u shunchaki mapped type:
Bu bobda aynan shu qatorni β [P in keyof T] nimaligini, undagi ? qanday "har maydonga" tarqalishini ochamiz. Bir marta tushunsangiz, Partial, Readonly, Pick, Recordni o'zingiz qayta yoza olasiz. Va undan ham kuchli β TypeScript'da tayyor turmagan, sizga kerakli tiplarni ham yasay olasiz. Boshlaymiz.
Mapped type β har maydon ustidan o'tish¶
JavaScript'da obyekt maydonlari ustidan for...in bilan aylanardingiz:
Mapped type β xuddi shuning tip darajasidagi ko'rinishi. Faqat qiymatlar ustidan emas, maydon nomlari (kalitlar) ustidan aylanasiz va har biri uchun yangi maydon yasaysiz. Sintaksis:
Uch bo'lakka ajratamiz (bu butun bobning kaliti):
keyof TβTtipining hamma maydon nomlarini union qilib beradi.Foydalanuvchiuchun bu"id" | "ism" | "email". (keyof12-bobda uchragan edi.)[K in ...]β o'sha union bo'ylab aylanadi:Knavbatma-navbat"id", keyin"ism", keyin"email"bo'ladi. Bufor...inning tip dunyosidagi aksi.T[K]βKmaydonning tipi (indexed access β "K maydoning tipi", 12-bob)."id"uchunnumber,"ism"uchunstring.
Eng oddiy mapped type β hech narsani o'zgartirmasdan nusxa ko'chiradigani:
type Foydalanuvchi = {
id: number;
ism: string;
email: string;
};
type Nusxa<T> = {
[K in keyof T]: T[K];
};
type FoydaNusxa = Nusxa<Foydalanuvchi>;
// natija: { id: number; ism: string; email: string } β aynan o'zi
Foydasi yo'qday tuyuladi, ammo bu β barcha utility tiplarning skeleti. Endi shu skeletga "o'zgartirish" qo'shamiz.
O'z Readonly va Partial'imizni yozish¶
Eng kuchli odat β modifikator qo'shish. readonly qo'shsangiz, har bir maydon o'zgarmaydigan bo'ladi:
type MyReadonly<T> = {
readonly [K in keyof T]: T[K];
};
type Qotgan = MyReadonly<Foydalanuvchi>;
// { readonly id: number; readonly ism: string; readonly email: string }
const q: Qotgan = { id: 1, ism: "Lola", email: "lola@x.uz" };
console.log(q.id); // β
o'qish β mayli
readonly so'zini bitta joyda yozdingiz, lekin u har maydonga tarqaldi. Endi qotgan maydonni o'zgartirsangiz:
Xuddi shu naqsh bilan Partialni ham yozamiz β faqat readonly o'rniga ?:
type MyPartial<T> = {
[K in keyof T]?: T[K];
};
type Yangilash = MyPartial<Foydalanuvchi>;
// { id?: number; ism?: string; email?: string }
const yangilash: Yangilash = { ism: "Aziz" }; // β
faqat bitta maydon β mayli
π ? maydonni ixtiyoriy qiladi, lekin tip tekshiruvini o'chirmaydi. Maydonni bersangiz, tipi to'g'ri bo'lishi shart:
const bad: Yangilash = { id: "salom" };
// β Xato: Type 'string' is not assignable to type 'number'.
π‘ Boshlovchilar tez-tez ? "har qanday qiymatni qabul qiladi" deb o'ylab xato qiladi. Yo'q β ? faqat "maydon bo'lmasa ham mayli" degani. Maydon bor bo'lsa, tipi qat'iy tekshiriladi.
Modifikatorlarni olib tashlash: -readonly va -?¶
Mapped type modifikator qo'sha oladi β demak teskari ham mumkin: olib tashlash. Buning uchun modifikator oldiga minus (-) qo'yiladi. Bu Required va Mutable (TypeScript'da bunday nomli utility yo'q, lekin foydali) tiplarini beradi:
// ? ni olib tashlaydi β hamma maydon majburiy bo'ladi
type MyRequired<T> = {
[K in keyof T]-?: T[K];
};
// readonly ni olib tashlaydi β hamma maydon o'zgaruvchan bo'ladi
type Mutable<T> = {
-readonly [K in keyof T]: T[K];
};
Tekshirib ko'ramiz. MyRequired ixtiyoriy maydonlarni majburiy qiladi:
type FoydaTahrir = { id?: number; ism?: string };
type Toliq = MyRequired<FoydaTahrir>;
// { id: number; ism: string } β ? lar yo'qoldi
const t: Toliq = { id: 1, ism: "Aziz" }; // β
endi ikkalasi ham majburiy
Mutable qotgan tipni yana yumshatadi:
type Mutatsiya = Mutable<Qotgan>; // Qotgan = MyReadonly<Foydalanuvchi>
// { id: number; ism: string; email: string } β readonly yo'qoldi
const m: Mutatsiya = { id: 1, ism: "A", email: "a@a.uz" };
m.ism = "B"; // β
endi o'zgartirsa bo'ladi
console.log(m.ism);
π Modifikatorlar grammatikasi:
| Yozuv | Ma'nosi |
|---|---|
readonly [K in ...] |
har maydonga readonly qo'shadi |
[K in ...]? |
har maydonni ixtiyoriy qiladi |
-readonly [K in ...] |
readonlyni olib tashlaydi |
[K in ...]-? |
?ni olib tashlaydi (majburiy qiladi) |
π‘ + ham bor (+readonly, +?), lekin u "qo'shish" degani β bu standart xulq, shuning uchun + deyarli yozilmaydi. - esa load-ko'taradigan, foydali shakl.
Key remapping: as bilan kalitlarni qayta nomlash¶
Hozirgacha maydon nomlari o'zgarmasdi β faqat tip yoki modifikator o'zgarardi. TypeScript 4.1 dan beri kalitning o'zini ham qayta nomlash mumkin β as orqali. Bu key remapping deyiladi.
Real misol: har maydon uchun getId, getIsm kabi "getter" metod nomlari yasashni xohlaysiz:
type Foydalanuvchi = {
id: number;
ism: string;
};
type Getterlar<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
type FoydaGetterlar = Getterlar<Foydalanuvchi>;
// { getId: () => number; getIsm: () => string }
Bu yerda as dan keyingi qism β yangi kalit nomi. `get${Capitalize<...>}` β template literal type (16-bobning mavzusi): "id"ni "getId"ga aylantiradi. Capitalize β birinchi harfni katta qiladigan tayyor utility.
const g: FoydaGetterlar = {
getId: () => 1,
getIsm: () => "Aziz",
};
console.log(g.getId(), g.getIsm()); // 1 Aziz
π string & K nega kerak? keyof T natijasi string | number | symbol bo'lishi mumkin, template literal esa faqat string bilan ishlaydi. string & K β "K ning faqat string qismini ol" degan kichik hiyla, Capitalizeni xotirjam qiladi.
Maydonni butunlay olib tashlash. as ning yana bir kuchi: agar yangi kalit never bo'lsa, o'sha maydon natijadan tushib qoladi. Bu bilan "filtrlash" mumkin β masalan, parol maydonini olib tashlash:
type Yashir<T, K extends keyof T> = {
[P in keyof T as P extends K ? never : P]: T[P];
};
type FoydaParolsiz = Yashir<{ id: number; ism: string; parol: string }, "parol">;
// { id: number; ism: string } β parol yo'q
Mantiq: har P uchun "agar P yashiriladigan kalit bo'lsa, nomini never qil (yo'qot), aks holda nomini o'zgarmasdan qoldir". never bo'lgan maydon tipdan chiqib ketadi.
const fp: FoydaParolsiz = { id: 1, ism: "X", parol: "123" };
// β Xato: 'parol' does not exist in type 'Yashir<...>'.
Conditional type β tip darajasidagi "if"¶
Endi ikkinchi katta qurolga o'tamiz. JavaScript'da qiymatlar bilan shart yozasiz:
TypeScript'da tiplar bilan shart yozish mumkin. Sintaksis bir xil β ?: β lekin shart extends so'zi bilan tuziladi:
O'qilishi: "agar T tipi stringga mos kelsa (extends β unga sig'sa), natija "ha" tipi, aks holda "yoq" tipi".
type A = IsString<string>; // "ha"
type B = IsString<number>; // "yoq"
const a: A = "ha"; // β
const b: B = "yoq"; // β
Natija β oddiy qiymat emas, tip. A tipi aynan "ha" literal tipi (7-bob). Shuning uchun boshqasini bersangiz, xato:
π extends bu yerda klassdagi "meros olish" emas. Tip dunyosida A extends B "A β B ning kichik to'plamimi, A ni B turgan joyga qo'ysa bo'ladimi?" degani. 42 extends number β ha (42 β number'ning bir qiymati). number extends 42 β yo'q (number 42'dan kengroq).
Conditionallarni zanjirlash mumkin β bir nechta if/else if kabi:
type Tip<T> = T extends string
? "satr"
: T extends number
? "raqam"
: T extends boolean
? "mantiq"
: "boshqa";
type T1 = Tip<string>; // "satr"
type T2 = Tip<number>; // "raqam"
type T3 = Tip<boolean>; // "mantiq"
type T4 = Tip<object>; // "boshqa"
π‘ Nullable kabi oddiy tiplarga conditional shart shart emas β ba'zan union yetadi:
type Nullable<T> = T | null;
type IsmYokiNull = Nullable<string>;
const n1: IsmYokiNull = "Lola"; // β
const n2: IsmYokiNull = null; // β
Conditional esa "null'ni olib tashlash" kabi murakkabroq ishlarda kerak bo'ladi:
type MyNonNullable<T> = T extends null | undefined ? never : T;
type Toza = MyNonNullable<string | null | undefined>; // string
const toza: Toza = "salom"; // β
Bu yerda never "bu holatni natijadan o'chir" degani (9-bob). Union ustidan qanday tarqalishini hozir ko'ramiz β ana shu sehrning kaliti.
infer β conditional ichidan tipni ajratib olish¶
Mana eng kuchli imkoniyat. Ko'pincha bizga shunday narsa kerak: "funksiya tipi berilgan β uning qaytaradigan tipini menga ber". Yoki "massiv tipi berilgan β uning element tipini ber". Buni infer (xulosa qil, ajratib ol) bajaradi.
infer faqat conditional type ichida ishlaydi. U extendsdagi naqsh ichiga "qopqon" qo'yadi β TypeScript o'sha joyni ko'rganda, qaysi tip turganini o'zi aniqlaydi va nomli tipga (R) joylaydi:
O'qilishi: "agar T qandaydir funksiya bo'lsa β qaytaradigan joyiga infer R qopqonini qo'yamiz; mos kelsa, R o'sha return tip bilan to'ladi va biz uni qaytaramiz; aks holda never".
function salomBer(): string {
return "salom";
}
type Natija = MyReturnType<typeof salomBer>; // string
const r: Natija = "salom"; // β
π Bu aynan TypeScript'ning o'rnatilgan ReturnType utilitysi yasalgan usul. ReturnType<typeof salomBer> ham string beradi. Endi siz uning ichini bilasiz β sehr emas, bir qator conditional + infer.
inferni boshqa joyga ham qo'yish mumkin β massiv elementini ajratish:
type ElementTipi<T> = T extends (infer E)[] ? E : never;
type SonElement = ElementTipi<number[]>; // number
type SatrElement = ElementTipi<string[]>; // string
Yoki Promise ichidagini "ochish" (Promise yechilganda qaytadigan tip):
type Unwrap<T> = T extends Promise<infer U> ? U : T;
type Yechilgan = Unwrap<Promise<string>>; // string
type Oddiy = Unwrap<number>; // number (Promise bo'lmasa o'zi qaytadi)
π‘ Bir conditional ichida bir nechta infer ishlatish ham mumkin. Masalan, funksiyaning birinchi argumenti tipini olish:
type FirstArg<T> = T extends (first: infer A, ...rest: any[]) => any ? A : never;
function qoshish(a: number, b: number): number {
return a + b;
}
type Birinchi = FirstArg<typeof qoshish>; // number
Distributive conditional β union ustidan tarqalish¶
Conditional type'ning bir "sirli" xususiyati bor: agar tekshirilayotgan tip (T) naked (yalang'och β o'rab olinmagan) union bo'lsa, conditional union har bir a'zosi ustidan alohida-alohida ishlaydi, keyin natijalar yana union bo'lib birlashadi. Bu distributive (taqsimlanuvchi) xulq.
Misol bilan ko'ramiz. Quyidagi tip har tipni massivga o'raydi:
Endi unga union beramiz:
type Natija = ToArray<string | number>;
// kutilgani: (string | number)[] ?
// haqiqati: string[] | number[]
Nima bo'ldi? T union bo'lgani uchun conditional alohida ishladi: avval string uchun (string[] chiqdi), keyin number uchun (number[] chiqdi), so'ng ikkalasi union bo'lib birlashdi β string[] | number[].
π Bu xulq faqat T yalang'och tip parametri bo'lganda yoqiladi (T extends ... ko'rinishida). Agar Tni biror narsa bilan o'rasangiz β masalan [T] tuple ichiga β tarqalish to'xtaydi va union bir butun bo'lib qoladi:
type ToArrayYagona<T> = [T] extends [any] ? T[] : never;
type Natija2 = ToArrayYagona<string | number>; // (string | number)[]
const c: Natija2 = ["x", 1, "y", 2]; // β
aralash massiv
Distributive xulq ko'p utility tiplarning asosida turadi. Masalan Exclude β union'dan ba'zi a'zolarni olib tashlash β aynan shunga tayanadi:
type MyExclude<T, U> = T extends U ? never : T;
type Qolgan = MyExclude<"a" | "b" | "c", "b">; // "a" | "c"
Mantiq: har a'zo uchun "agar u Uga kirsa β never (o'chir), aks holda β qoldir". "b" uchun never chiqdi va union'dan tushdi, qolganlari saqlanib qoldi. Bu ham TypeScript'ning haqiqiy Exclude ta'rifi.
π‘ never union'da g'oyib bo'ladi: "a" | never | "c" avtomatik "a" | "c"ga soddalashadi. Shu sabab "o'chirish" never bilan ishlaydi.
Hammasini birga: DeepReadonly¶
Endi o'rgangan hamma narsani bitta foydali tipga jamlaymiz. Readonly faqat yuza qatlamni qotiradi β ichki obyektlar baribir o'zgaruvchan qoladi:
type Sozlama = {
nom: string;
server: { host: string; port: number };
};
const s: Readonly<Sozlama> = { nom: "prod", server: { host: "x", port: 5432 } };
s.nom = "dev"; // β Xato β yuza maydon qotgan
s.server.port = 8080; // β
(!) ichki maydon hali ham o'zgaradi
Ko'pincha bizga chuqur (rekursiv) qotirish kerak. Mapped + conditional + rekursiya buni hal qiladi:
type DeepReadonly<T> = {
readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K];
};
E'tibor bering, mapped type o'zini-o'zi chaqirayapti (DeepReadonly<T[K]>) β bu rekursiya. Har maydon uchun: "agar uning tipi obyekt bo'lsa β ichiga kirib, yana DeepReadonly qo'lla; aks holda (oddiy string, number...) β tegmasdan qoldir".
type Sozlama = {
nom: string;
server: {
host: string;
port: number;
opsiyalar: { ssl: boolean };
};
};
const s: DeepReadonly<Sozlama> = {
nom: "prod",
server: { host: "localhost", port: 5432, opsiyalar: { ssl: true } },
};
console.log(s.server.opsiyalar.ssl); // β
o'qish β mayli
Endi har qancha chuqurdagi maydon ham qotgan:
π T[K] extends object shartida object β funksiya, massiv, oddiy obyekt β barchasini qamraydi. Massivlar bilan ham ishlaydi (massiv ham object), shuning uchun ichidagi elementlar ham readonly bo'ladi. Murakkabroq holatlar (funksiyani chetlab o'tish va h.k.) uchun shartni aniqlashtirish mumkin, lekin bu skelet ko'pchilik holga yetadi.
π‘ Mana sizning to'la "asboblar to'plami": mapped type maydonlar ustidan aylanadi, modifikatorlar ularni yumshatadi/qotiradi, as qayta nomlaydi/filtrlaydi, conditional qaror qabul qiladi, infer ichidan ajratadi, distributive union ustidan tarqaladi, rekursiya chuqurga kiradi. Shu yetti tushuncha bilan TypeScript'dagi deyarli har qanday utility tipni qayta yozasiz yoki yangisini yasaysiz.
Qachon ishlatish (va qachon yo'q)¶
Bu kuchli vositalar, lekin har joyga tiqishtirmaslik kerak:
- Tayyor utility bor bo'lsa β uni ishlat.
Partial,Readonly,Pick,Record,ReturnType,Exclude,NonNullableallaqachon mavjud (14-bob). O'zingiznikini faqat tayyori yo'q bo'lganda yozing. - Mapped/conditional β kutubxona va umumiy yordamchi tiplar uchun. Oddiy ilova kodida sizga ko'pincha sodda
interfacevatypeyetadi. Tip darajasidagi "dasturlash" β qayta ishlatiladigan, ko'p joyda asqotadigan abstraksiyalar uchun. - O'qilishini o'ylang. Uch qavat ichma-ich conditional +
inferkuchli, lekin jamoadoshingiz tushunmasa, foydadan ko'ra zarari ko'p. Murakkab tipga qisqa sharh yozib qo'ying.
π Umumiy qoida: "agar tipni qo'lda yozish bir-ikki joy uchun bo'lsa β qo'lda yoz; agar manba tip o'zgarganda avtomatik moslashishi kerak bo'lsa β mapped/conditional bilan bog'lab qo'y". Asosiy yutuq β bitta manbadan kelib chiqadigan, sinxron yuradigan tiplar.
15-bob mashqlari¶
Quyidagi mashqlarni o'zingiz bajaring β yechim berilmagan. Har birini alohida .ts faylga yozib, tsc --noEmit --strict bilan tekshiring.
-
Foydalanuvchitipini yarating (id,ism,email). Mapped type bilan hech narsani o'zgartirmaydiganNusxa<T>yozing vaNusxa<Foydalanuvchi>natijasi asl tip bilan bir xilligiga ishonch hosil qiling. -
O'zingizning
MyReadonly<T>tipingizni yozing. UniFoydalanuvchiga qo'llang va biror maydonni o'zgartirib ko'ring βtscxato berishini tasdiqlang (// βbilan belgilang). -
O'zingizning
MyPartial<T>tipingizni yozing. Faqat bitta maydonni beruvchi obyekt yarating β xato chiqmasligini, lekin noto'g'ri tip berilsa xato chiqishini ko'ring. -
MyRequired<T>tipini-?modifikatori bilan yozing. Kirishda hamma maydoni ixtiyoriy bo'lgan tipni bering va natijada hammasi majburiy bo'lishini tekshiring. -
Mutable<T>tipini-readonlybilan yozing.MyReadonlybilan qotirilgan tipni unga bering va endi maydonni o'zgartirsa bo'lishini tasdiqlang. -
T[K](indexed access) yordamidaFoydalanuvchitipidan faqatemailmaydonining tipini chiqaruvchiEmailTipitip aliasini yozing. -
Key remapping (
as) bilanGetterlar<T>yozing: har maydon uchungetXxx: () => tipshaklidagi metod nomlari hosil bo'lsin.Capitalizeva template literal'dan foydalaning. -
Key remapping va
neverbilanYashir<T, K>yozing:Kkalitidagi maydonni natijadan butunlay olib tashlasin.parolmaydonli tipdan uni yashirib ko'ring. -
IsString<T>conditional tipini yozing:stringuchun"ha", aks holda"yoq"qaytarsin. Unistring,number,booleanbilan sinab ko'ring. -
To'rt tarmoqli zanjirli conditional
Tip<T>yozing:string/number/boolean/qolgani uchun mos label qaytarsin. -
Nullable<T>tipini union (T | null) ko'rinishida yozing. Keyin unistringga qo'llab,nullva satrning ikkalasi ham qabul qilinishini tekshiring. -
MyNonNullable<T>conditional tipini yozing:nullvaundefinedni olib tashlasin.string | null | undefinedga qo'llab, natija faqatstringbo'lishini ko'ring. -
MyReturnType<T>niinferbilan yozing. Bir funksiya yarating va uning return tipinitypeoforqali ajratib oling. Natijani o'rnatilganReturnTypebilan solishtiring. -
ElementTipi<T>niinferbilan yozing: massiv tipidan element tipini ajratsin.number[]vastring[]bilan sinab ko'ring. -
inferbilanFirstArg<T>yozing: funksiyaning birinchi argumenti tipini ajratib bersin. Ikki argumentli funksiyada sinang. -
Unwrap<T>niinferbilan yozing:Promise<X>bo'lsaXni, aks holdaTning o'zini qaytarsin.Promise<string>vanumberbilan sinang. -
Distributive
ToArray<T>yozing (T extends any ? T[] : never). Ungastring | numberbering va natijastring[] | number[]ekanini tasdiqlang. -
17-mashqdagi tipni
[T] extends [any]ko'rinishida o'rab, distributiyani to'xtating. Endi natija(string | number)[]bo'lishini ko'ring va aralash massiv bilan tekshiring. -
Distributiyaga tayanib
MyExclude<T, U>yozing."a" | "b" | "c"dan"b"ni olib tashlang va natija"a" | "c"ekanini tasdiqlang. -
DeepReadonly<T>ni mapped + conditional + rekursiya bilan yozing. Ichida obyekt bo'lgan ko'p qatlamliSozlamatipini bering va eng ichki maydonni o'zgartirib ko'ring βtscxato berishini tasdiqlang.