12 — Generics — ilg'or (keyof, indexed access)¶
⬅️ Oldingi: 11 — Generics — asoslar · 🏠 README · Keyingi: 13 — Klasslar TypeScript'da ➡️
Bu bobda: generic'larni obyektning ichiga olib kiramiz.
keyof T(obyekt kalitlari union'i), indexed accessT[K](kalitning qiymat tipi) va bularni birlashtirganK extends keyof Tconstraint'i (cheklovi) bilan TypeScript tarixidagi eng mashhur yordamchi — xavfsizget— ni yozamiz. Yo'lda default tip parametri<T = string>, qiymatdan tip oladigantypeofoperatori,consttip parametri va real loyihada ishlatiladigan tiplanganpluck/pickyordamchilarini ko'rib chiqamiz. Bularning hammasiPick,Record,ReturnTypekabi utility type'lar (keyingi bob) tagida turgan poydevor.
Muammo¶
11-bobda generic funksiya bitta narsani — turli tiplar bilan ishlay oladigan bir funksiya yozishni — o'rgatdi. Lekin u yerda biz odatda butun qiymatni olib, butunligicha qaytarardik: identity<T>(x: T): T. Endi esa boshqacha vaziyat: obyektni emas, uning bitta maydonini olishimiz kerak. JavaScript'da bu ish bir qatorda bitadi:
function get(obyekt, kalit) {
return obyekt[kalit];
}
const kitob = { id: 1, nomi: "Otkan kunlar", yil: 1925 };
get(kitob, "nomi"); // "Otkan kunlar"
get(kitob, "narxi"); // undefined — xato emas, jim qoladi!
Ikki muammo bor. Birinchisi: get(kitob, "narxi") — bunday maydon yo'q, lekin JavaScript hech narsa demaydi, undefined qaytaradi va xato keyinroq, mutlaqo boshqa joyda "portlaydi". Ikkinchisi: TypeScript'da bu funksiyaga eng sodda tip yozsak, qaytadigan qiymat tipi yo'qoladi:
function get(obyekt: object, kalit: string): unknown {
return (obyekt as Record<string, unknown>)[kalit];
}
const nomi = get(kitob, "nomi");
// nomi: unknown — string ekanini TypeScript bilmaydi!
get(kitob, "nomi") aniq string qaytarishini biz bilamiz, lekin TypeScript bilmaydi — natija unknown (9-bob). Demak nomi.toUpperCase() deb yozolmaymiz. Funksiya "ishlaydi", lekin tip jihatidan ko'r. Bizga shunday get kerakki, u:
- faqat mavjud kalitni qabul qilsin ("narxi" — kompilyatsiyada xato);
- qaytadigan tip aynan o'sha maydonniki bo'lsin (
"nomi"->string,"yil"->number).
Bu ikki talabni bajarish uchun TypeScript'ning ikki tip-operatori kerak: keyof va indexed access. Avval ularni alohida o'rganamiz, keyin birlashtiramiz.
keyof T — obyekt kalitlari union'i¶
keyof — bu tip operatori: obyekt tipini olib, uning hamma kalit nomlaridan iborat literal union (16-bobdagi string literal tiplar union'i) qaytaradi.
type Kitob = {
id: number;
nomi: string;
yil: number;
bepulmi: boolean;
};
type KitobKalit = keyof Kitob;
// "id" | "nomi" | "yil" | "bepulmi"
Endi KitobKalit tipidagi o'zgaruvchiga faqat shu to'rt qatordan birini berish mumkin:
📌 keyof — bu maydon nomlarini beradi, qiymatlarini emas. keyof Kitob natijasi "id" | "nomi" | ... (matnlar), 1925 yoki "Otkan kunlar" emas. Bu juda muhim farq: keyof tip darajasida ishlaydi, sizning haqiqiy kitob obyektingizga umuman tegmaydi.
💡 keyofning eng kuchli tarafi — avtomatik yangilanishi. Kitob tipiga yangi maydon (masalan muallif: string) qo'shsangiz, keyof Kitob darrov ... | "muallif" ga kengayadi. Hech bir joyda union'ni qo'lda yangilash kerak emas — bu DRY (Don't Repeat Yourself) tamoyilining tip darajasidagi ko'rinishi.
Indexed access T[K] — kalitdan qiymat tipini olish¶
keyof bizga kalitlarni berdi. Endi teskari savol: "shu kalitning qiymati qanaqa tipda?". Bunga indexed access (indeksli kirish) javob beradi — sintaksisi massivdan element olishga juda o'xshaydi, lekin u tip ustida ishlaydi:
E'tibor bering: kvadrat qavs ichida tip darajasidagi kalit turibdi ("nomi" — string literal tip), oddiy o'zgaruvchi emas. Bir nechta kalit (union) bersangiz, natija ham qiymat tiplarining union'i bo'ladi:
Va eng nafis qolip — keyof bilan birlashtirib, obyektning barcha qiymat tiplarini olish:
📌 Eng ko'p uchraydigan tuzoq: tip yozuvida nuqta ishlamaydi. Kitob.nomi deb yozsangiz, TypeScript "bu tipda nomi nomli alohida tip yo'q" deb xato beradi. Tipni faqat kvadrat qavs bilan olamiz: Kitob["nomi"]. Buni shunday eslang: nuqta — qiymat dunyosi (kitob.nomi haqiqiy obyektdan), kvadrat qavs — tip dunyosi (Kitob["nomi"] tipdan).
// ❌ Xato (TS2713): Cannot access 'Kitob.nomi' because 'Kitob' is a type, but not a namespace.
// type NomiTipi = Kitob.nomi;
✅ Tipni ham, kalitni ham qo'lda yozmaslik mumkin: Kitob[keyof Kitob] butun obyekt o'zgarsa ham o'zicha to'g'ri qoladi.
Xavfsiz get — keyof + indexed access + constraint¶
Endi ikki qismni bitta funksiyaga yig'amiz. Eslang, generic constraint (cheklov) — K extends keyof T — 11-bobda ko'rgan extends kalit so'zining aynan o'zi: "K — bu T ning kalitlaridan biri bo'lishi shart".
Bu bitta qatorni diqqat bilan o'qib chiqaylik — TypeScript'da eng ko'p uchraydigan generic naqshi shu:
T— obyektning tipi (uni TypeScript chaqiruvdan o'zi topadi);K extends keyof T—KfaqatTning kalitlaridan biri bo'la oladi;: T[K]— qaytadigan tip aynan o'sha kalitning qiymat tipi.
Natijada funksiya har bir chaqiruvda aniq tipni qaytaradi:
const kitob: Kitob = { id: 1, nomi: "Otkan kunlar", yil: 1925, bepulmi: false };
const nomi = get(kitob, "nomi"); // nomi: string
const yil = get(kitob, "yil"); // yil: number
const bepul = get(kitob, "bepulmi"); // bepul: boolean
const katta = nomi.toUpperCase(); // ✅ nomi string — to'g'ri
const keyingiYil = yil + 1; // ✅ yil number — to'g'ri
Endi muammomizning birinchi yarmi — noto'g'ri kalit. get yo'q kalitni qabul qilmaydi, xato ishga tushishdan oldin chiqadi:
const x = get(kitob, "narx");
// ❌ Xato: Argument of type '"narx"' is not assignable
// to parameter of type 'keyof Kitob'.
Ikkinchi yarmi — yo'qolgan tip — ham hal bo'ldi: nomi endi unknown emas, aniq string:
const teskari = get(kitob, "nomi");
const xato = teskari * 2;
// ❌ Xato: The left-hand side of an arithmetic operation must be
// of type 'any', 'number', 'bigint' or an enum type.
📌 get<T, K extends keyof T>da extends "meros" (klass merosi) ma'nosida emas. Generic'da extends "...ga bo'ysunadi / ...ning qism to'plami" degani. K extends keyof T — "K, keyof T union'idagi qiymatlardan biri" demak. Buni 8-bobdagi narrowing bilan ham adashtirmang — bu yerda gap kalit qiymatlari haqida.
💡 Nega K umuman kerak, nega shunchaki kalit: keyof T yozmaymiz? Sinab ko'ring: agar get(obyekt: T, kalit: keyof T): T[keyof T] yozsangiz, funksiya yana ishlaydi, lekin natija number | string | boolean (barcha qiymatlar union'i) bo'lib qoladi — qaysi kalit so'ralganini "unutadi". Alohida K parametri har bir chaqiruvni alohida eslab qoladi: "nomi" -> string, "yil" -> number. Aynan shu narsa getni "aqlli" qiladi.
Teskari yo'nalish: set (T[K] qiymat tomonida)¶
get qiymatni oladi, set esa qiymatni o'rnatadi. Bu yerda T[K] argument tipi sifatida ishlatiladi — ya'ni TypeScript siz beradigan qiymat o'sha maydonga mos kelishini tekshiradi:
function set<T, K extends keyof T>(obyekt: T, kalit: K, qiymat: T[K]): void {
obyekt[kalit] = qiymat;
}
set(kitob, "yil", 1926); // ✅ yil number, 1926 number — mos
set(kitob, "bepulmi", true); // ✅ bepulmi boolean, true boolean — mos
Endi noto'g'ri tipdagi qiymat berib ko'ring — kompilyator ushlaydi:
set(kitob, "yil", "1926");
// ❌ Xato: Argument of type 'string' is not assignable
// to parameter of type 'number'.
📌 set ham, get ham bir xil <T, K extends keyof T> skeletidan foydalanadi — farqi faqat T[K] qayerda turishida: getda u chiqish (return) tipi, setda esa kirish (argument) tipi. Bitta tip-formula ikki tomonga ham ishlaydi — bu generic'larning go'zalligi.
Default tip parametri: <T = string>¶
Funksiya argumentiga standart qiymat berish mumkin bo'lgani kabi (function f(x = 10)), generic tip parametriga ham standart tip berish mumkin. Agar chaqiruvchi tipni ko'rsatmasa, o'sha default ishlatiladi:
type Javob<T = string> = {
ok: boolean;
data: T;
};
const j1: Javob = { ok: true, data: "salom" }; // T berilmadi -> string
const j2: Javob<number> = { ok: true, data: 42 }; // T = number
const j3: Javob<Kitob> = { ok: true, data: kitob }; // T = Kitob
Javob (default bilan) — Javob<string>ning qisqartmasi. Bu, ayniqsa, API javoblari uchun qulay: ko'p hollarda matn qaytadi, ba'zan boshqa tip.
📌 Default tip parametri faqat tip berilmasa ishlaydi. Javob<number> yozsangiz, default string butunlay e'tiborga olinmaydi. Bu funksiya argumentidagi default bilan bir xil mantiq.
💡 Default va constraint birga ham kelishi mumkin: <T extends object = {}> — "T obyekt bo'lsin, berilmasa bo'sh obyekt". Tartib doim shunday: avval extends (cheklov), keyin = (default).
typeof — qiymatdan tip olish¶
Ko'pincha sizda allaqachon bir obyekt (qiymat) bor va aynan o'shanga mos tip kerak bo'ladi. Tipni qo'lda qaytadan yozish — zerikarli va xatoga moyil (qiymat o'zgarsa, tip eskirib qoladi). typeof operatori qiymatdan to'g'ridan-to'g'ri tip chiqaradi:
const sozlama = {
til: "uz",
qorongu: false,
shrift: 14,
};
type Sozlama = typeof sozlama;
// { til: string; qorongu: boolean; shrift: number }
function ishlat(s: Sozlama): void {
console.log(s.til, s.qorongu, s.shrift);
}
ishlat(sozlama);
Bu yerda ikki xil typeof borligini ajrating. JavaScript'dagi typeof x — bu ishga tushganda ishlaydigan, "string" kabi matn qaytaradigan operator. TypeScript'dagi typeof esa faqat tip yozuvida (tip kontekstida) yashaydi va qiymatdan tip chiqaradi. Bir xil so'z, ikki dunyo — typeof tip yozuvida (masalan type X = typeof ... yoki annotatsiyada) turganini bilib qoling.
typeofni keyof bilan birlashtirsa, qiymatdan to'g'ridan-to'g'ri uning kalitlari union'i chiqadi — bu juda ko'p ishlatiladigan qolip:
type SozlamaKalit = keyof typeof sozlama;
// "til" | "qorongu" | "shrift"
const sk: SozlamaKalit = "shrift"; // ✅
O'qilishi (ichdan tashqariga): typeof sozlama -> sozlamaning tipi; keyof (...) -> o'sha tipning kalitlari.
typeof funksiya ustida ham ishlaydi — funksiyaning to'liq imzosini (signature) tip sifatida oladi:
function kitobYarat(nomi: string, yil: number) {
return { id: Date.now(), nomi, yil, faolmi: true };
}
type KitobYaratTipi = typeof kitobYarat;
// (nomi: string, yil: number) => { id: number; nomi: string; yil: number; faolmi: boolean }
💡 Bu yerda kelajakka bir ko'prik: typeof kitobYarat funksiyaning butun imzosini berdi. Ko'pincha bizga faqat uning qaytaradigan qismi kerak bo'ladi. Buni ReturnType<typeof kitobYarat> beradi:
type YaratilganKitob = ReturnType<typeof kitobYarat>;
// { id: number; nomi: string; yil: number; faolmi: boolean }
ReturnType — bu utility type, va u aynan shu bobdagi keyof, T[K] va extends mexanikasi ustiga qurilgan. Keyingi bobdan keyingi 14-bobda utility type'larni to'liq ochamiz; hozir shuni bilib qo'ying — ularning "sehri" siz hozir o'rgangan g'ishtlardan iborat.
Tiplangan yordamchilar: pluck va pick¶
Endi nazariyani real loyihada uchraydigan ikki funksiyaga aylantiramiz.
pluck — massiv ichidagi har bir obyektdan bitta maydonni sug'urib oladi (Lodash kutubxonasidagi _.map(arr, "key") ning tiplangan ko'rinishi):
function pluck<T, K extends keyof T>(massiv: T[], kalit: K): T[K][] {
return massiv.map((element) => element[kalit]);
}
const kitoblar: Kitob[] = [
{ id: 1, nomi: "Otkan kunlar", yil: 1925, bepulmi: false },
{ id: 2, nomi: "Mehrobdan chayon", yil: 1929, bepulmi: true },
];
const nomlar = pluck(kitoblar, "nomi"); // string[]
const yillar = pluck(kitoblar, "yil"); // number[]
E'tibor bering: qaytadigan tip T[K][] — ya'ni "kalit qiymatining massivi". "nomi" bersangiz string[], "yil" bersangiz number[]. Shuning uchun keyingi qadamda to'g'ri metodlardan foydalanish mumkin:
const birlashgan = nomlar.join(", "); // ✅ string[] da .join bor
const summa = yillar.reduce((a, b) => a + b, 0); // ✅ number[] qo'shiladi
pick — obyektdan tanlangan bir nechta maydonni olib, kichikroq obyekt yasaydi. Bu yerda kalit massiv bo'lgani uchun K[], natija esa Pick<T, K> (standart utility type — "T dan faqat K kalitlarini ol"):
function pick<T, K extends keyof T>(obyekt: T, kalitlar: K[]): Pick<T, K> {
const natija = {} as Pick<T, K>;
for (const kalit of kalitlar) {
natija[kalit] = obyekt[kalit];
}
return natija;
}
const qisqa = pick(kitob, ["id", "nomi"]);
// qisqa: { id: number; nomi: string }
const qisqaNomi = qisqa.nomi; // ✅ string
const qisqaId = qisqa.id; // ✅ number
📌 pickda {} as Pick<T, K> — bu type assertion (3- va 9-boblardagi as). Tsiklning boshida obyekt bo'sh, lekin biz unga Pick<T, K> tipini "va'da" qilamiz va tsikl ichida hamma maydonni to'ldiramiz. Bu — TypeScript'ning oddiy oqim tahlili "ko'rolmaydigan" kam sonli o'rinlardan biri, shuning uchun as bilan yo'naltiramiz. (Pick — keyingi 14-bobning asosiy mavzularidan.)
💡 Faqat string emas, raqamli kalit ham keyof orqali yaxshi ifodalanadi. Masalan, pluckni Foydalanuvchi[] ustida ishlatib, faqat email larni yig'ish — bir qator: pluck(users, "email") -> string[]. Generic kod bir marta yoziladi, hamma joyda tipli qoladi.
const tip parametri (zamonaviy imkoniyat)¶
Odatda TypeScript literal qiymatlarni "kengaytiradi": ["a", "b"] ni string[] deb biladi ("a" emas). Lekin ba'zan bizga aynan literal tip kerak. TypeScript 5.0 dan beri const tip parametri shuni hal qiladi — chaqiruvda as const yozmasdan:
function birinchi<const T>(massiv: readonly T[]): T {
return massiv[0];
}
const x = birinchi(["a", "b", "c"]);
// x: "a" | "b" | "c" (oddiy string emas!)
<const T> bo'lmaganida x shunchaki string bo'lardi. const modifikatori TypeScript'ga "argumentni iloji boricha tor (literal) tipda ushla" deydi.
📌 const tip parametri — yangi (TS 5.0+) va o'rinli ishlatilganda foydali, lekin har joyda kerak emas. Faqat literal tiplarni saqlash muhim bo'lganda (masalan, konfiguratsiya kalitlari, marshrut nomlari) qo'llang. Aks holda oddiy generic yetarli.
Hammasini bir joyda: nega bu muhim?¶
Bu bobdagi to'rt g'isht — keyof, T[K], K extends keyof T va typeof — alohida-alohida kichik tuyuladi. Lekin ular birga TypeScript'ning eng kuchli xususiyatining poydevori: bir tipdan ikkinchi tipni hisoblab chiqarish. Siz endi get, set, pluck, pick kabi to'liq tiplangan, xato kalit yozsangiz darrov ogohlantiradigan yordamchilarni yoza olasiz. Va eng muhimi — keyingi boblarda uchraydigan Pick, Omit, Record, ReturnType kabi utility type'lar aslida shu mexanikadan tug'iladi. Ularni ishlatganda endi "sehr" emas, mantiq ko'rasiz.
12-bob mashqlari¶
Quyidagi mashqlarni o'zingiz bajaring. Har birini alohida .ts faylga yozib, tsc --noEmit --strict bilan tekshiring: toza misollar xatosiz o'tsin, ataylab xatolilar haqiqatan xato bersin.
Mahsulot = { id: number; nomi: string; narx: number }tipini yarating vakeyof Mahsulotnatijasini izoh sifatida yozing.type MahsulotKalit = keyof Mahsulotdeb alias yarating, unga to'g'ri qiymat ("nomi") va noto'g'ri qiymat ("rang") bering — ikkinchisi xato berishini tasdiqlang.- Indexed access bilan
Mahsulot["narx"]tipini oling va o'sha tipdagi o'zgaruvchi e'lon qiling. Mahsulot["id" | "nomi"]union tipini oling; natijasi nima bo'lishini izohda yozing.Mahsulot[keyof Mahsulot]yordamida barcha qiymat tiplari union'ini oling.Mahsulot.narx(nuqta bilan) deb tip yozishga urinib ko'ring va chiqqan xatoni izohga ko'chiring; keyinMahsulot["narx"]ga tuzating.get<T, K extends keyof T>(o: T, k: K): T[K]funksiyasini yozing vaMahsulotobyektidannarxni oling — natijanumberekanini tekshiring.- Shu
getbilan mavjud bo'lmagan kalit ("rang") so'rang; chiqqan xato xabarini izohga yozing. getqaytargannomi(string) ustida.toUpperCase()chaqiring — o'tishini tasdiqlang; keyin uni* 2qilib xato chiqaring.set<T, K extends keyof T>(o: T, k: K, q: T[K]): voidfunksiyasini yozing;narxga to'g'ri (number) va noto'g'ri (string) qiymat berib, ikkinchisi xato berishini ko'ring.type Javob<T = string>ni yarating;Javob,Javob<number>vaJavob<Mahsulot>uchun uchta to'g'ri obyekt yozing.<T extends object = {}>ko'rinishidagi default + constraint birga turgan generic funksiya yozing va izohlang.const sozlama = { til: "uz", soni: 3 }qiymatidantypeof sozlamabilan tip oling va o'sha tipdagi yangi obyekt yarating.keyof typeof sozlamabilan kalitlar union'ini oling va unga faqat to'g'ri kalit beriladigan o'zgaruvchi e'lon qiling.- Bir funksiya yozing (
kitobYaratkabi) vatypeof funksiyabilan uning to'liq imzo tipini oling. - 15-mashqdagi funksiya uchun
ReturnType<typeof funksiya>tipini oling va izohda nima chiqishini yozing. pluck<T, K extends keyof T>(massiv: T[], kalit: K): T[K][]ni yozing;Mahsulot[]dannarxlarni yig'ibnumber[]olganingizni.reducebilan tasdiqlang.- Shu
pluckbilannomilarni yig'ibstring[]oling va.join(", ")qiling. pick<T, K extends keyof T>(o: T, k: K[]): Pick<T, K>ni yozing;Mahsulotdan["id", "nomi"]ni ajratib oling va natija obyektiningnarxmaydoniga murojaat qilib (yo'q bo'lgani uchun) xato chiqishini ko'ring.consttip parametri bilanbirinchi<const T>(m: readonly T[]): Tyozing;["o'qildi", "o'qilmadi"]bering va natijastringemas, literal union ("o'qildi" | "o'qilmadi") ekanini hover bilan tekshiring.