IlmHamroh
JavaScript Full-stack/15-qism. TypeScript27/47-dars24 daqiqa
Mundarija (29)

TypeScript mapped types: { [K in keyof T]: … } bilan turdan tur yasash

Qisqacha: Mapped type — mavjud turning har bir kaliti bo'ylab "aylanib", yangi tur yasaydigan yozuv. Masalan, { [K in keyof T]: boolean } — "T dagi har bir K kalit uchun boolean maydon". Tayyor Partial, Readonly, Record, Pick aynan shunday yozilgan. Belgilar: -readonly va -? modifikatorni olib tashlaydi, as esa kalitni qayta nomlaydi yoki o'chiradi.

Bu darsda

  • Mapped type yozasiz va kompilyator uni qanday hisoblashini qadamma-qadam ko'rsata olasiz.
  • Partial, Required, Readonly, Record ni o'zingiz yozib, kutubxonadagi asli bilan solishtirasiz.
  • +/- bilan readonly va ? ni qo'shasiz yoki olib tashlaysiz.
  • as bilan kalitlarni filtrlaysiz va qayta nomlaysiz.
  • Utility types darsidagi Partial + spread tuzog'i nega paydo bo'lishini mapped type ichidan tushuntira olasiz.

Oldin bilishingiz kerak: Utility types, keyof, typeof va indexed access, Generic cheklovlar va keyof.

1. Nega bu kerak?

O'tgan darsda Partial<MenuItem>, Readonly<MenuItem>, Record<Day, …> kabi tayyor "tur-funksiyalar"ni ishlatdik. Ular qanday ishlaydi? Va o'zimizga tayyorida yo'q o'zgartirish kerak bo'lsa-chi? Bugun shu savollarga javob beramiz. Bu dars va keyingi beshta dars — tur darajasidagi "dasturlash". Boshida g'alati tuyulishi tabiiy, shuning uchun har bir turni kompilyator qanday hisoblashini jadvalda qadamma-qadam ko'ramiz.

«Bahor» oshxonasining admin panelida menyu formasi bor. Sardor taom turini yozgan:

ts
type MenuItem = {
  name: string;
  price: number;
  available: boolean;
};

const item: MenuItem = { name: "Osh", price: 35000, available: true };
console.log(item.name); // Osh

Formaga yana ikkita narsa kerak bo'ldi. Birinchisi — har maydon uchun xato matni ("Narx musbat bo'lsin"). Ikkinchisi — "foydalanuvchi bu maydonga tegdimi" belgilari. Sardor ularni qo'lda yozdi:

ts
type MenuItemErrors = {
  name: string;
  price: string;
  available: string;
};

const errors: MenuItemErrors = {
  name: "",
  price: "Narx musbat bo'lsin",
  available: "",
};
console.log(errors.price); // Narx musbat bo'lsin

Bir hafta o'tdi. Jasur aka "achchiq taomlar belgisi kerak" dedi va Sardor MenuItem ga spicy: boolean qo'shdi. MenuItemErrors ni esa unutdi. TypeScript indamadi: ikki tur bir-biriga hech qanday bog'lanmagan, ular ikki alohida qog'ozga yozilgan ro'yxatdek. Formada "achchiq" maydonining xatosi so'ralganda undefined chiqdi.

Muammo — takrorlanish: bitta ma'lumot (maydonlar ro'yxati) ikki joyda yozilgan. Bir joy o'zgarsa, ikkinchisi eskirib qoladi. Mapped type ikkinchi ro'yxatni birinchisidan hisoblab chiqaradi:

ts
type MenuItem = {
  name: string;
  price: number;
  available: boolean;
  spicy: boolean;
};
type MenuItemErrors = { [K in keyof MenuItem]: string };

const errors: MenuItemErrors = {
  name: "",
  price: "Narx musbat bo'lsin",
  available: "",
};

Kompilyator:

text
TS2741: Property 'spicy' is missing in type '{ name: string; price: string; available: string; }' but required in type 'MenuItemErrors'.

Tarjimasi: "spicy xususiyati { name: string; … } turida yo'q, lekin MenuItemErrors turida majburiy". Endi MenuItemErrors — MenuItem dan yasalgan. MenuItem ga maydon qo'shilsa, xatolar turi ham o'zi kengayadi va tsc unutilgan joyni darhol ko'rsatadi.

2. Birinchi mapped type

2.1 Yozuvni o'qiymiz

Mapped type (xaritalangan tur) — boshqa turning kalitlari bo'ylab yurib, har biri uchun yangi maydon yasaydigan tur. Kunlik o'xshatish: davomat jadvali. O'qituvchi sinf ro'yxatini oladi va har bir ism yoniga "keldi/kelmadi" katagini chizadi. Ro'yxat — keyof T, katak — yangi tur.

ts
type MenuItem = {
  name: string;
  price: number;
  available: boolean;
};
type Flags<T> = { [K in keyof T]: boolean };

const changed: Flags<MenuItem> = {
  name: true,
  price: false,
  available: false,
};
console.log(changed.name); // true

{ [K in keyof T]: boolean } ni bo'laklab o'qiymiz:

Bo'lak Ma'nosi
keyof T T ning hamma kalitlari — union
K in … "shu union'dagi har bir K uchun"
[K in …] kvadrat qavs: kalit nomi — K ning o'zi
: boolean har kalitning qiymat turi

K — o'zgaruvchi nomi, xuddi for (const key of keys) dagi key kabi. Uni P, Key yoki Field deb atash ham mumkin. Farqi — JavaScript sikli ishlash paytida obyekt kalitlari bo'ylab yuradi, mapped type esa kompilyatsiya paytida tur kalitlari bo'ylab yuradi. Ishlash paytida undan hech narsa qolmaydi.

2.2 Kompilyator qanday hisoblaydi

Flags<MenuItem> ni tsc shunday ochadi:

Qadam Kompilyator nima qiladi Natija
1 T o'rniga MenuItem qo'yadi { [K in keyof MenuItem]: boolean }
2 keyof MenuItem ni hisoblaydi "name" | "price" | "available"
3 K = "name" name: boolean
4 K = "price" price: boolean
5 K = "available" available: boolean
6 maydonlarni bitta obyekt turiga yig'adi { name: boolean; price: boolean; available: boolean }

Rasmda xuddi shu jarayon:

flowchart LR
  A["MenuItem"] --> B["keyof: name, price, available"]
  B --> C["K = name"]
  B --> D["K = price"]
  B --> E["K = available"]
  C --> F["name: boolean"]
  D --> G["price: boolean"]
  E --> H["available: boolean"]
  F --> I["Flags natijasi"]
  G --> I
  H --> I

Bitta maydon tushib qolsa, natija turi uni majburiy deb talab qiladi:

ts
type MenuItem = { name: string; price: number; available: boolean };
type Flags<T> = { [K in keyof T]: boolean };

const changed: Flags<MenuItem> = { name: true, price: false };

Kompilyator:

text
TS2741: Property 'available' is missing in type '{ name: true; price: false; }' but required in type 'Flags<MenuItem>'.

Tarjimasi: "available xususiyati { name: true; price: false; } da yo'q, lekin Flags<MenuItem> da majburiy". Xabarda Flags<MenuItem> deb yozilgan — tsc turni ochib o'tirmaydi, nomini ko'rsatadi. Ochilgan ko'rinishini VS Code'da sichqonchani tur nomi ustiga olib borib (hover) ko'rasiz.

2.3 T[K] — qiymat turini saqlash

boolean o'rniga T[K] yozsak — har kalit o'zining asl turini oladi. T[K] — indexed access: "T ning K kalitidagi tur".

ts
type MenuItem = { name: string; price: number; available: boolean };
type Copy<T> = { [K in keyof T]: T[K] };

const same: Copy<MenuItem> = {
  name: "Manti",
  price: 30000,
  available: true,
};
console.log(same.price + 5000); // 35000
Qadam K T[K] Maydon
1 "name" MenuItem["name"] = string name: string
2 "price" MenuItem["price"] = number price: number
3 "available" MenuItem["available"] = boolean available: boolean

Copy<MenuItem> — MenuItem ning aynan nusxasi. O'zi foydasiz, lekin endi har maydonga bitta narsa qo'shsak — kutubxonadagi tur-funksiyalarning o'zi chiqadi.

Tekshirib ko'ring: type Prices = { osh: number; manti: number } uchun { [K in keyof Prices]: string } qanday tur bo'ladi?

Javob

{ osh: string; manti: string }. Kalitlar Prices dan olinadi ("osh" | "manti"), qiymat turi esa hamma kalit uchun bir xil — string. T[K] yozilmagani uchun asl number turlari ishlatilmadi.

3. Partial, Readonly va Required ichidan

3.1 Kutubxonadagi asl ta'riflar

TypeScript o'rnatilganda u bilan lib.es5.d.ts fayli keladi — tilning tayyor turlari shu yerda. Utility types darsidagi tur-funksiyalarning haqiqiy ta'rifi — TypeScript 7.0.2 bilan kelgan fayldan aynan:

ts
type Partial<T> = {
    [P in keyof T]?: T[P];
};

type Required<T> = {
    [P in keyof T]-?: T[P];
};

type Readonly<T> = {
    readonly [P in keyof T]: T[P];
};

Hammasi — Copy<T> ning o'zi, bitta qo'shimcha bilan:

  • Partial — kalitdan keyin ?: har maydon ixtiyoriy.
  • Readonly — kalitdan oldin readonly: har maydon faqat o'qish uchun.
  • Required — -?: ixtiyoriylikni olib tashlaydi.

Kalit nomi bu yerda P (property — xususiyat). Nomi ahamiyatsiz, K bilan bir xil.

3.2 + va -: modifikatorni qo'shish va olib tashlash

Modifikator (modifier) — maydon oldidagi yoki ortidagi belgi: readonly va ?. Mapped type'da ular oldiga + yoki - qo'yiladi:

Yozuv Ma'nosi
readonly [K in …] yoki +readonly hamma maydonni readonly qil
-readonly [K in …] readonly ni olib tashla
[K in …]? yoki +? hamma maydonni ixtiyoriy qil
[K in …]-? ixtiyoriylikni olib tashla

+ — standart, odatda yozilmaydi. - esa yangi narsa. «Bahor» dagi voqea: serverdan kelgan menyu Readonly — uni tasodifan buzib bo'lmaydi. Lekin tahrirlash formasi uchun o'zgartirsa bo'ladigan qoralama nusxa kerak. Tilda -readonly bajaradigan tayyor tur yo'q, uni o'zimiz yozamiz:

ts
type MenuItem = { name: string; price: number; available: boolean };
type Mutable<T> = { -readonly [K in keyof T]: T[K] };

const saved: Readonly<MenuItem> = {
  name: "Osh",
  price: 35000,
  available: true,
};
saved.price = 36000;

const draft: Mutable<Readonly<MenuItem>> = { ...saved };
draft.price = 36000;

Kompilyator:

text
TS2540: Cannot assign to 'price' because it is a read-only property.

Bitta xato — saved.price qatorida: "price faqat o'qish uchun, unga qiymat berib bo'lmaydi". draft.price = 36000 esa xatosiz o'tdi. Kompilyator Mutable<Readonly<MenuItem>> ni ichkaridan tashqariga hisoblaydi:

Qadam Hisob Natija
1 Readonly<MenuItem> { readonly name: string; readonly price: number; … }
2 Mutable<…>: har K uchun -readonly readonly olib tashlanadi
3 yig'adi { name: string; price: number; available: boolean }

-? ham xuddi shunday. Forma qoralamasi Partial<MenuItem> — maydonlar asta-sekin to'ldiriladi. Saqlashdan oldin esa hammasi bo'lishi shart — Required:

ts
type MenuItem = { name: string; price: number; available: boolean };
type Draft = Partial<MenuItem>;

const ready: Required<Draft> = { name: "Manti", price: 30000 };

Kompilyator:

text
TS2741: Property 'available' is missing in type '{ name: string; price: number; }' but required in type 'Required<Partial<MenuItem>>'.

Tarjimasi: "available yo'q, lekin Required<Partial<MenuItem>> da majburiy". Required<Partial<MenuItem>> — avval ? qo'shildi, keyin -? uni olib tashladi. Natija yana asl MenuItem ga teng.

3.3 Asl modifikatorlar saqlanadi

Mayda, lekin muhim detal. { [K in keyof T]: … } ko'rinishidagi mapped type T dagi ? va readonly ni o'zi ko'chiradi — siz +/- yozmasangiz ham:

ts
type Dish = { name: string; price?: number; readonly id: number };
type Flags<T> = { [K in keyof T]: boolean };
type DishFlags = Flags<Dish>;

const marks: DishFlags = { name: true, id: false };
console.log(Object.keys(marks)); // [ 'name', 'id' ]

VS Code'da DishFlags nomi ustiga sichqonchani olib borsangiz (hover), ochilgan tur ko'rinadi:

text
type DishFlags = {
    name: boolean;
    price?: boolean | undefined;
    readonly id: boolean;
}

price ixtiyoriyligicha qoldi, shuning uchun marks da uni yozmasak ham bo'ldi. Xuddi shunday, id ham readonly qoldi. keyof T dan yasalgan mapped type'ni TypeScript "asl turning shakli" deb hisoblaydi va modifikatorlarni meros qilib oladi. Bunday turning nomi — gomomorf (homomorphic) mapped type, ya'ni "shaklni saqlaydigan". Shuning uchun Partial<Readonly<T>> dagi readonly yo'qolmaydi.

Yana bir kutilmagan holat: T tuple yoki massiv bo'lsa, natija ham tuple bo'ladi — obyekt emas:

ts
type Flags<T> = { [K in keyof T]: boolean };
const pair: Flags<[string, number]> = [true, false];
console.log(pair.length); // 2

Hover: [boolean, boolean]. Kompilyator tuple'ning length, push kabi metodlarini aylanmaydi — faqat elementlarini. Bu Readonly<[string, number]> ning readonly [string, number] bo'lishining sababi ham shu.

Tekshirib ko'ring: Readonly<Partial<MenuItem>> da price qanday ko'rinadi?

Javob

readonly price?: number | undefined. Ichkaridagi Partial ? qo'shdi, tashqaridagi Readonly esa readonly qo'shdi va ? ni meros qilib saqladi (gomomorf). Ikkala modifikator birga turadi.

4. Kalitlar keyof dan bo'lishi shart emas: Record ichidan

in dan keyin istalgan kalitlar union'i turishi mumkin — keyof shart emas. «Bahor» ish vaqti haftaning har kuni uchun alohida:

ts
type Day = "dushanba" | "seshanba" | "chorshanba";
type Hours = { [D in Day]: [open: number, close: number] };

const hours: Hours = {
  dushanba: [7, 23],
  seshanba: [7, 23],
};

Kompilyator:

text
TS2741: Property 'chorshanba' is missing in type '{ dushanba: [number, number]; seshanba: [number, number]; }' but required in type 'Hours'.

Tarjimasi: "chorshanba ikki kunlik obyektda yo'q, lekin Hours da majburiy". Kompilyator Hours ni shunday yig'gan:

Qadam Hisob Natija
1 in Day — union'dagi har bir satr "dushanba", "seshanba", "chorshanba"
2 har biriga bir xil qiymat turi [open: number, close: number]
3 yig'adi uch majburiy maydonli obyekt

Day ga yangi kun qo'shsangiz, ish vaqti jadvalida u yo'qligini tsc darhol aytadi. Bu Record ning o'zi:

ts
type Record<K extends keyof any, T> = {
    [P in K]: T;
};

K extends keyof any — cheklov (Generic cheklovlar): "K kalit bo'la oladigan narsa bo'lsin". keyof any — string | number | symbol, ya'ni JavaScript obyekt kaliti bo'la oladigan hamma turlar. Shuning uchun Record<boolean, string> yozib bo'lmaydi: true obyekt kaliti emas.

To'g'ri yozilgan jadval:

ts
type Day = "dushanba" | "seshanba" | "chorshanba";
type Hours = Record<Day, [open: number, close: number]>;

const hours: Hours = {
  dushanba: [7, 23],
  seshanba: [7, 23],
  chorshanba: [10, 22],
};
console.log(hours.chorshanba[0]); // 10

Chorshanba — sanitariya kuni: oshxona 10:00 da ochilib, 22:00 da yopiladi.

5. as bilan kalitni qayta nomlash va filtrlash

5.1 Kalitni o'chirish: never

Ba'zan hamma kalit kerak emas. Yangi taom qo'shilganda id ni server beradi — formada uni so'ramaymiz. Key remapping (kalitni qayta xaritalash) — as bilan har kalitni boshqa kalitga almashtirish. Qoida: kalit never ga almashsa, u o'chadi.

ts
type MenuItem = { id: number; name: string; price: number };
type NoId<T> = { [K in keyof T as Exclude<K, "id">]: T[K] };

const newItem: NoId<MenuItem> = { name: "Somsa", price: 8000 };
console.log(Object.keys(newItem).length); // 2

Exclude — "union'dan shu a'zoni chiqar". tsc har kalit uchun shunday hisoblaydi:

K Exclude<K, "id"> Natijada
"id" never o'chadi
"name" "name" name: string
"price" "price" price: number

Natija: { name: string; price: number }. id ni berib ko'ring:

ts
type MenuItem = { id: number; name: string; price: number };
type NoId<T> = { [K in keyof T as Exclude<K, "id">]: T[K] };

const newItem: NoId<MenuItem> = { id: 7, name: "Somsa", price: 8000 };

Kompilyator:

text
TS2353: Object literal may only specify known properties, and 'id' does not exist in type 'NoId<MenuItem>'.

"Obyekt literalida faqat ma'lum xususiyatlar bo'lishi mumkin, id esa NoId<MenuItem> turida yo'q". Bu — ortiqcha xususiyat tekshiruvi (Obyekt turlari).

NoId sizga tanish ko'rinyaptimi? Bu Omit<MenuItem, "id"> bilan bir xil natija. Kutubxonada Omit boshqacha yozilgan — Pick<T, Exclude<keyof T, K>> — lekin g'oyasi bir: kerakmas kalitni Exclude bilan chiqarish.

5.2 Kalitni qayta nomlash

Forma kutubxonalarida xato maydonlari ko'pincha nameError, priceError deb nomlanadi. as dan keyin yangi nomni shablon bilan yasash mumkin:

ts
type MenuItem = { name: string; price: number };
type ErrorFields<T> = {
  [K in keyof T as `${K & string}Error`]?: string;
};

const errors: ErrorFields<MenuItem> = { priceError: "Narx bo'sh" };
console.log(errors.priceError); // Narx bo'sh

Backtick ichidagi `${K & string}Error` — tur darajasidagi shablon satr: "K ning matni + Error". Bu yozuvni Template literal types darsida to'liq o'rganamiz. Hozir bitta narsani bilish yetarli: K & string — "K ning satr bo'lgan qismi" (kesishma). Nega kerak — «Ko'p uchraydigan xatolar» bo'limida.

K Yangi kalit Maydon
"name" "nameError" nameError?: string
"price" "priceError" priceError?: string

Tekshirib ko'ring: { [K in keyof MenuItem as Exclude<K, "name" | "price">]: MenuItem[K] } — MenuItem = { id: number; name: string; price: number } bo'lsa, natija qanday?

Javob

{ id: number }. Exclude ikkala kalitni ham never ga aylantirdi, ular o'chdi. Qolgani — id. Bu Pick<MenuItem, "id"> bilan bir xil.

6. TypeScript ushlamaydigan tuzoq: Partial va spread — ichidan

Utility types darsida bu tuzoqni ko'rgansiz: PATCH'dagi { price: undefined } spread bilan eski narxni o'chiradi. «Bahor» admin panelidagi o'sha holat:

ts
type MenuItem = { name: string; price: number; available: boolean };

function applyPatch(
  base: MenuItem,
  patch: Partial<MenuItem>,
): MenuItem {
  return { ...base, ...patch };
}

const osh: MenuItem = { name: "Osh", price: 35000, available: true };
const updated = applyPatch(osh, { price: undefined });
console.log(updated.price.toFixed(0));

tsc — xatosiz. Node esa:

text
TypeError: Cannot read properties of undefined (reading 'toFixed')

Endi sababini mapped type ichidan ko'ra olasiz. Partial ning ta'rifi — [P in keyof T]?: T[P]. Kompilyator price uchun shunday hisoblaydi:

Qadam Hisob Natija
1 T[P] — MenuItem["price"] number
2 ? qo'shildi — bayroqsiz ? "yo'q yoki undefined" degani price?: number | undefined
3 { price: undefined } tekshirildi o'tdi
4 Spread natijasini TypeScript "ixtiyoriy — demak yo'q bo'lishi mumkin" deb hisobladi price: number (yolg'on)

Spread esa undefined ni ham ko'chiradi — 3-qadamdagi qiymat eski 35000 ni o'chirdi. Bunday qiymat forma maydoni tozalanganda tabiiy paydo bo'ladi: { price: input.value ? Number(input.value) : undefined }.

Himoya — o'sha darsdagi exactOptionalPropertyTypes bayrog'i: u 2-qadamdagi | undefined ni qo'shmaydi, applyPatch(osh, { price: undefined }) esa TS2379 beradi. Bu bayroq strict ga kirmaydi, lekin TypeScript 7 ning tsc --init i uni yoqib qo'yadi — batafsil Strict bayroqlar oilasi darsida. Bayroqsiz loyihada — qo'lda: undefined qiymatli kalitlarni spread'dan oldin olib tashlang yoki maydonni alohida ko'chiring (price: patch.price ?? base.price).

Tekshirib ko'ring: applyPatch(osh, {}) va applyPatch(osh, { price: undefined }) — ikkalasi ham tsc dan o'tadi (bayroqsiz). Ishlash paytida natijalari farq qiladimi?

Javob

Farq qiladi. Bo'sh {} da price kaliti umuman yo'q — spread hech narsa ko'chirmaydi, narx 35000 bo'lib qoladi. Ikkinchisida kalit bor, qiymati undefined — spread uni ko'chiradi va narxni o'chiradi. TypeScript bayroqsiz bu ikki holatni bir xil ko'radi, JavaScript esa farqlaydi. exactOptionalPropertyTypes shu farqni turga olib kiradi.

7. Mapped type qachon kerak emas

Yangi asbobni o'rgangach, uni hamma joyda ishlatgisi keladi. Lekin har bir mapped type kod o'quvchisidan bir qadam ko'proq o'ylashni talab qiladi: hover'ni ochish, keyof ni hisoblash, modifikatorlarni eslash.

Oddiy qoida: mapped type'ni bir xil maydonlar ro'yxati ikki va undan ko'p joyda takrorlansa yozing. Formada xatolar, "tegilgan" belgilari va qoralama — uchta tur bitta MenuItem dan kelib chiqadi, demak foydali. Bitta joyda ishlatiladigan, boshqa turga bog'liq bo'lmagan shakl uchun esa oddiy obyekt turi aniqroq.

Ikkinchi qoida: tayyor tur bo'lsa — o'zingiznikini yozmang. Partial, Readonly, Pick, Omit, Record ni har TypeScript dasturchisi taniydi. NoId<T> ni esa birinchi ko'rgan odam ochib o'qishi kerak — uning o'rniga Omit<T, "id"> yozing. O'z mapped type'ingiz tayyorida yo'q ish uchun kerak: -readonly, kalitni qayta nomlash, har maydon turini o'zgartirish (T[K] | null).

8. Ko'p uchraydigan xatolar

8.1 Mapped type'ga oddiy maydon qo'shish

ts
type MenuItem = { name: string; price: number };
type Flags<T> = { [K in keyof T]: boolean; updatedAt: number };

Kompilyator:

text
TS7061: A mapped type may not declare properties or methods.

"Mapped type xususiyat yoki metod e'lon qila olmaydi". Mapped type — faqat bitta [K in …] qatoridan iborat. Tuzatish: qo'shimcha maydonni kesishma bilan ulang: { [K in keyof T]: boolean } & { updatedAt: number }.

Xuddi shu sababdan mapped type'ni interface ichida yozib bo'lmaydi — u faqat type bilan. interface Flags<T> { [K in keyof T]: boolean } yozsangiz, tsc sintaksis xatolari beradi (TS1005: ']' expected.).

8.2 in dan keyin keyof ni unutish

ts
type Flags<T> = { [K in T]: boolean };

Kompilyator:

text
TS2322: Type 'T' is not assignable to type 'string | number | symbol'.

Tarjimasi: "T turini string | number | symbol turiga berib bo'lmaydi" — ya'ni "T kalit bo'la olmaydi". in dan keyin kalitlar turishi kerak. T — obyekt turi, kalit emas. Tuzatish: [K in keyof T]. (Agar T ning o'zi kalitlar union'i bo'lishi kerak bo'lsa — Record dagidek cheklov qo'ying: <T extends PropertyKey>; PropertyKey — string | number | symbol ning tayyor nomi.)

8.3 Shablonda K & string ni unutish

ts
type ErrorFields<T> = { [K in keyof T as `${K}Error`]?: string };

Kompilyator:

text
TS2322: Type 'K' is not assignable to type 'string | number | bigint | boolean | null | undefined'.
  Type 'keyof T' is not assignable to type 'string | number | bigint | boolean | null | undefined'.
    Type 'string | number | symbol' is not assignable to type 'string | number | bigint | boolean | null | undefined'.
      Type 'symbol' is not assignable to type 'string | number | bigint | boolean | null | undefined'.

Zanjirni pastdan o'qing: "symbol ni shablonga qo'yib bo'lmaydi". keyof T da symbol kalitlar ham bo'lishi mumkin, ularni esa matnga aylantirib bo'lmaydi (JavaScript'da ham `${Symbol()}` — TypeError). Tuzatish: `${K & string}Error` — faqat satr kalitlar olinadi, symbol lar never bo'lib o'chadi.

8.4 Object.keys bilan mapped type to'ldirish

ts
type MenuItem = { name: string; price: number };
type Flags<T> = { [K in keyof T]: boolean };

function markAll(item: MenuItem): Flags<MenuItem> {
  const flags = {} as Flags<MenuItem>;
  for (const key of Object.keys(item)) {
    flags[key] = true;
  }
  return flags;
}

Kompilyator:

text
TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'Flags<MenuItem>'.
  No index signature with a parameter of type 'string' was found on type 'Flags<MenuItem>'.

Tarjimasi: "string turidagi ifoda bilan Flags<MenuItem> ga indeks berib bo'lmaydi". Object.keys — string[], keyof MenuItem emas. Sababi strukturaviy tiplash: item da turida yo'q qo'shimcha kalitlar ham bo'lishi mumkin. Mapped type ishlash paytida obyekt yasamaydi — uni siz yasaysiz. Tuzatish: kalitlar ro'yxatini o'zingiz yozing (const FIELDS = ["name", "price"] as const) yoki obyektni literal bilan to'liq yozing — shunda tsc hamma kalit borligini tekshiradi (mashqdagi kabi).

8.5 Partial + spread

«TypeScript ushlamaydigan tuzoq» bo'limidagi va Utility types darsidagi holat. Tuzatish: exactOptionalPropertyTypes yoki undefined larni tozalab spread.

9. Mashqlar

1-mashq (oson): Natija turini ayting

ts
type Seat = { table: number; guest: string };
type Flags<T> = { [K in keyof T]: boolean };
type Wrapped<T> = { [K in keyof T]: T[K][] };

const seen: Flags<Seat> = { table: true, guest: false };
const history: Wrapped<Seat> = { table: [3, 5], guest: ["Malika"] };
console.log(seen.table, history.guest.length); // true 1

Flags<Seat> da guest ning turi: [:boolean]. Wrapped<Seat> da table ning turi — sonlar massivi: number[]. Wrapped<Seat> da nechta maydon bor? [:2]

Yechim

Flags har kalitga boolean beradi — guest: boolean. Wrapped esa asl turni massivga o'raydi: T["table"] — number, demak table: number[], guest: string[]. Maydonlar soni o'zgarmaydi — 2 ta, chunki keyof Seat da ikkita kalit.

2-mashq (o'rta): Nullable va o'zingizning Pick

Ikki tur yozing:

  1. Nullable<T> — har maydon null ham bo'la olsin (price: number | null). Ishora: T[K] | null.
  2. OnlyKeys<T, K extends keyof T> — faqat K kalitlar qolsin (Pick ni o'zingiz yozing). Ishora: in dan keyin keyof T emas, K turadi — xuddi Record dagidek.
Yechim
ts
type MenuItem = { id: number; name: string; price: number };

type Nullable<T> = { [K in keyof T]: T[K] | null };
type OnlyKeys<T, K extends keyof T> = { [P in K]: T[P] };

const unknownPrice: Nullable<MenuItem> = {
  id: 9,
  name: "Norin",
  price: null,
};
const short: OnlyKeys<MenuItem, "name" | "price"> = {
  name: "Lag'mon",
  price: 28000,
};
console.log(unknownPrice.price ?? "narx hali yo'q"); // narx hali yo'q
console.log(short.price); // 28000

OnlyKeys — kutubxonadagi Pick bilan harfma-harf bir xil: type Pick<T, K extends keyof T> = { [P in K]: T[P]; }. K extends keyof T cheklovi OnlyKeys<MenuItem, "rating"> ni taqiqlaydi — "rating" MenuItem kaliti emas. Nullable gomomorf — MenuItem da ? bo'lsa, u saqlanardi.

3-mashq (qiyin): Amaliy tajriba — menyu formasi

Mashq kurs/mashqlar/15/ papkasida (Tur annotatsiyasi darsida tayyorlangan umumiy tsconfig.json bilan). 27-mapped/menu-form.ts faylini yarating:

  • MenuItem — name, price, available (hozircha uchta maydon).
  • FieldErrors<T> — har kalit uchun ixtiyoriy xato matni (string).
  • Touched<T> — har kalit uchun majburiy boolean.
  • NOTHING_TOUCHED: Touched<MenuItem> — hamma maydon false.
  • validateItem(item) — FieldErrors<MenuItem> qaytaradi: bo'sh nom (bo'shliqlar ham) — "Nom bo'sh bo'lmasin", narx musbat butun son bo'lmasa — "Narx musbat butun son bo'lsin".
  • hasErrors(errors) — kamida bitta xato bormi. Ishora: Object.values va some.

Yozib bo'lgach, MenuItem ga spicy: boolean qo'shing va npx tsc --pretty false ni ishga tushiring. Qaysi joy xato beradi, qaysi biri o'zi moslashadi? Keyin 27-mapped/menu-form.test.ts da testlar yozing: to'g'ri taom, bo'sh nom bilan manfiy narx, kasr narx va NOTHING_TOUCHED kalitlari taom kalitlari bilan bir xilligi.

Yechim

spicy qo'shilgach tsc bitta joyni ko'rsatdi:

text
27-mapped/menu-form.ts(14,14): error TS2741: Property 'spicy' is missing in type '{ name: false; price: false; available: false; }' but required in type 'Touched<MenuItem>'.

NOTHING_TOUCHED — qo'lda yozilgan obyekt, unga yangi maydon qo'shish kerak. FieldErrors va validateItem esa o'zi moslashdi: FieldErrors<MenuItem> endi spicy?: string ni ham biladi. Mapped type'ning foydasi aynan shu — tur o'zi kengayadi, qiymatlarni esa tsc tekshirib turadi.

ts
// 27-mapped/menu-form.ts — «Bahor» menyu formasi
export type MenuItem = {
  name: string;
  price: number;
  available: boolean;
  spicy: boolean;
};

// Har maydonning xato matni (bo'lmasligi ham mumkin)
export type FieldErrors<T> = { [K in keyof T]?: string };
// Har maydon: foydalanuvchi unga tegdimi?
export type Touched<T> = { [K in keyof T]: boolean };

export const NOTHING_TOUCHED: Touched<MenuItem> = {
  name: false,
  price: false,
  available: false,
  spicy: false,
};

export function validateItem(item: MenuItem): FieldErrors<MenuItem> {
  const errors: FieldErrors<MenuItem> = {};
  if (item.name.trim() === "") {
    errors.name = "Nom bo'sh bo'lmasin";
  }
  if (!Number.isInteger(item.price) || item.price <= 0) {
    errors.price = "Narx musbat butun son bo'lsin";
  }
  return errors;
}

export function hasErrors<T>(errors: FieldErrors<T>): boolean {
  return Object.values(errors).some((text) => text !== undefined);
}

hasErrors — generic (Generics): u istalgan formaning xatolari bilan ishlaydi. Object.values(errors) turi — (string | undefined)[].

ts
// 27-mapped/menu-form.test.ts
import { test } from "node:test";
import assert from "node:assert/strict";
import {
  validateItem,
  hasErrors,
  NOTHING_TOUCHED,
} from "./menu-form.ts";
import type { MenuItem } from "./menu-form.ts";

const osh: MenuItem = {
  name: "Osh",
  price: 35000,
  available: true,
  spicy: false,
};

test("validateItem: to'g'ri taomda xato yo'q", () => {
  assert.deepEqual(validateItem(osh), {});
  assert.equal(hasErrors(validateItem(osh)), false);
});

test("validateItem: bo'sh nom va manfiy narx", () => {
  const errors = validateItem({ ...osh, name: "  ", price: -1 });
  assert.equal(errors.name, "Nom bo'sh bo'lmasin");
  assert.equal(errors.price, "Narx musbat butun son bo'lsin");
  assert.equal(hasErrors(errors), true);
});

test("validateItem: kasr narx ham xato", () => {
  const errors = validateItem({ ...osh, price: 35000.5 });
  assert.deepEqual(Object.keys(errors), ["price"]);
});

test("NOTHING_TOUCHED: hamma maydon false", () => {
  assert.deepEqual(Object.keys(NOTHING_TOUCHED), Object.keys(osh));
  assert.ok(Object.values(NOTHING_TOUCHED).every((v) => v === false));
});

import type { MenuItem } — faqat tur import qilinadi (Modullar TypeScript'da darsida batafsil; umumiy tsconfig.json dagi verbatimModuleSyntax buni talab qiladi). Endi npx tsc --pretty false — chiqishsiz. node --test 27-mapped/menu-form.test.ts bizda (Node 24.21.0) shunday chiqdi:

text
✔ validateItem: to'g'ri taomda xato yo'q (6.9476ms)
✔ validateItem: bo'sh nom va manfiy narx (0.8971ms)
✔ validateItem: kasr narx ham xato (1.0409ms)
✔ NOTHING_TOUCHED: hamma maydon false (1.0348ms)
ℹ tests 4
ℹ suites 0
ℹ pass 4
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 413.7714

XATOLAR.md ga bu darsning yangi xabarlarini qo'shing: TS7061 (mapped type'da oddiy maydon), TS2322 (in T — kalit emas; shablonda symbol), TS2741 ning mapped type'dagi ko'rinishi. Keyin kurs/mashqlar papkasidan:

bash
git add 15/27-mapped 15/XATOLAR.md
git commit -m "15/27: menu-form.ts — mapped types bilan forma turlari"

10. Real ishda

  • Forma kutubxonalari (React Hook Form, Formik — React bilan 17-qismdan keyin uchratasiz) xatolar, "tegilgan" va "o'zgargan" belgilari turlarini aynan shunday yasaydi: { [K in keyof FormValues]?: … }. Siz formaning shaklini bir marta yozasiz, qolgan turlar undan hisoblanadi.
  • Kutubxona turlari — lib.es5.d.ts dagi Partial, Readonly, Record, Pick — hammasi mapped type. Endi ularni "sehr" emas, oddiy kod sifatida o'qiy olasiz.
  • API turlari: "yaratish uchun" (id siz), "yangilash uchun" (hammasi ixtiyoriy), "javob" (hammasi readonly) — bitta asosiy turdan mapped type bilan chiqariladi. Masalan, vazifalar dagi Partial<Omit<VazifaMalumoti, "id">> (api.ts, Utility types qadami) — shuning namunasi.
  • Intervyu: "Partial ni o'zingiz yozing", "-readonly nima qiladi?", "Omit va Exclude farqi?" — o'rta darajadagi TypeScript intervyusining odatiy savollari.

Xulosa

  • Mapped type — { [K in Kalitlar]: Tur }: kalitlar bo'ylab tur darajasida "sikl". Ishlash paytida undan hech narsa qolmaydi.
  • keyof T bilan — mavjud turning shakli; T[K] bilan — asl qiymat turi. Partial, Readonly, Required, Pick, Record — shunday yozilgan.
  • readonly va ? oldiga +/-: qo'shish va olib tashlash (-readonly, -?). keyof T li mapped type asl modifikatorlarni saqlaydi.
  • as — kalitni qayta nomlash; never ga aylangan kalit o'chadi.
  • Partial ixtiyoriy maydonga undefined ni ham ruxsat beradi, spread esa uni ko'chiradi. exactOptionalPropertyTypes buni ushlaydi.

Keyingi dars: Conditional types — tur darajasidagi if: T extends U ? X : Y. Bugun ishlatgan Exclude qanday yozilganini o'sha yerda ko'ramiz.

Manbalar

  • TypeScript Handbook: "Mapped Types" — typescriptlang.org/docs/handbook/2/mapped-types.html
  • TypeScript Release Notes 2.8 (mapping modifiers +/-) va 4.1 (key remapping as) — typescriptlang.org/docs/handbook/release-notes/
  • TSConfig: exactOptionalPropertyTypes — typescriptlang.org/tsconfig#exactOptionalPropertyTypes
  • lib.es5.d.ts — TypeScript bilan keladigan fayl (Partial, Required, Readonly, Pick, Record)
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
TypeScript mapped types: { [K in keyof T]: … } bilan turdan tur yasash — IlmHamroh