IlmHamroh
JavaScript Full-stack/15-qism. TypeScript17/47-dars23 daqiqa
Mundarija (38)

TypeScript index signature va Record: kalitlari noma'lum obyektlar

Qisqacha: Kalitlari oldindan noma'lum obyekt (lug'at) shunday tiplanadi: { [dish: string]: number } yoki qisqasi Record<string, number>. Kalitlar ro'yxati aniq bo'lsa — Record<"osh" | "manti", number>: unda hamma kalit majburiy. Tuzoq: prices["lagmon"] turi number, haqiqatda esa undefined bo'lishi mumkin — noUncheckedIndexedAccess yoki Map himoya qiladi.

Bu darsda

  • Index signature ([kalit: string]: T) yozasiz va uning cheklovlarini bilasiz.
  • Record<K, V> ni ikki xil ishlatasiz: istalgan kalitli lug'at va aniq kalitlar ro'yxati.
  • Lug'atdagi yashirin undefined ni ko'rasiz va noUncheckedIndexedAccess bilan ushlaysiz.
  • Nega interface Record<string, unknown> ga mos kelmasligini tushuntira olasiz.
  • Qachon obyekt o'rniga Map olish kerakligini tanlay olasiz.

Oldin bilishingiz kerak: Kengaytirish, kesishma va declaration merging, Massiv va tuple turlari, Map, Union va literal turlar.

1. Nega bu kerak?

O'tgan darsda maydonlari aniq turlarni birlashtirdik: name, price, cookMinutes. Lekin ba'zi obyektlarda kalitlar oldindan ma'lum emas. Masalan, «Bahor» narxlar ro'yxati: taom nomi — kalit, narx — qiymat. Ertaga Jasur aka yangi taom qo'shadi, kalitlar ko'payadi.

Sardor buyurtma summasini hisoblaydi. Kassir "lagmon" ni "lag'mon" deb yozib yubordi:

js
const prices = { osh: 35000, manti: 30000, lagmon: 28000 };
const order = ["osh", "lag'mon"];
const total = order.reduce((sum, name) => sum + prices[name], 0);
console.log(total); // NaN

prices["lag'mon"] — undefined, summa esa NaN. Bunday obyekt lug'at (dictionary) deyiladi: kitob lug'atidagi kabi, so'z bo'yicha qiymat topiladi. Lug'atda kalit bo'lmasligi — odatiy holat, xato emas. Bu darsda lug'atni to'g'ri tiplashni va "kalit yo'q" holatini TypeScript'ga ko'rsatishni o'rganamiz.

2. Index signature: "har qanday kalit, bir xil qiymat"

2.1 Yozuv

Lug'at turi shunday yoziladi:

ts
type PriceList = { [dish: string]: number };

const prices: PriceList = { osh: 35000, manti: 30000 };
prices["lagmon"] = 28000;
console.log(Object.keys(prices).length); // 3

[dish: string]: number — index signature (indeks imzosi): "istalgan satr kalit, qiymati — son". Massiv turlari darsida readonly massivning "indeks imzosi" xabarini ko'rgansiz — bu o'sha narsa, endi obyekt uchun. Kvadrat qavs ichidagi dish — faqat nom, hujjat uchun: [key: string] yoki [x: string] deb yozsangiz ham tur bir xil. Lekin ma'noli nom kodni o'qishni osonlashtiradi.

Endi qiymat turi tekshiriladi:

ts
type PriceList = { [dish: string]: number };
const prices: PriceList = { osh: 35000, manti: "30 000" };

Kompilyator:

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

Tarjimasi: "string turini number turiga berib bo'lmaydi". TypeScript manti ning qiymatiga qarab aytyapti: "bu yerda son kutgandim, matn keldi". Kalit istalgancha bo'lishi mumkin, lekin har bir qiymat son bo'lishi shart.

2.2 Qaysi kalit turlari mumkin?

Kalit turi faqat string, number, symbol yoki shablon satr turi (pastroqda ko'ramiz) bo'la oladi. Literal union yozib ko'ramiz:

ts
type DishName = "osh" | "manti";
type Prices = { [dish: DishName]: number };

Kompilyator:

text
TS1337: An index signature parameter type cannot be a literal type or generic type. Consider using a mapped object type instead.

Tarjimasi: "Index signature parametri literal yoki generic tur bo'la olmaydi. O'rniga mapped object turidan foydalaning". Oddiy qilib: kvadrat qavs ichiga aniq nomlar ro'yxatini yozib bo'lmaydi. Xabardagi ikki yangi so'zdan qo'rqmang. Generic — "turi keyin beriladigan tur", uni Generics darsida o'rganamiz. Mapped type — Mapped types darsida. Lekin uning eng ko'p ishlatiladigan tayyor shakli — Record — shu darsning o'zida, «Record<K, V>» bo'limida.

Kalit turi sifatida shablon satr turi (template literal type) ham mumkin. Bu JavaScript'dagi `Salom, ${ism}` shablonining tur darajasidagi egizagi: ${…} o'rnida qiymat emas, tur turadi. U bilan kalitning ko'rinishini cheklash mumkin. Masalan, HTML'dagi data- atributlari (Atributlar, xususiyatlar va dataset):

ts
type DataAttrs = { [attr: `data-${string}`]: string };
const attrs: DataAttrs = { "data-id": "7", "data-dish": "osh" };
console.log(Object.keys(attrs)); // [ 'data-id', 'data-dish' ]

`data-${string}` — "data- bilan boshlanadigan istalgan satr". Boshqa kalit yozilsa:

ts
type DataAttrs = { [attr: `data-${string}`]: string };
const attrs: DataAttrs = { "data-id": "7", title: "Osh" };

Kompilyator:

text
TS2353: Object literal may only specify known properties, and 'title' does not exist in type 'DataAttrs'.

Tarjimasi: "Obyekt literalida faqat ma'lum xususiyatlar bo'lishi mumkin, title esa DataAttrs turida yo'q". Bunday shablon turlarini Template literal types darsida chuqur o'rganamiz.

number kalitli imzo ham bor: { [table: number]: string } — "stol raqami → ofitsiant". Lekin yodda tuting: JavaScript obyektida kalitlar baribir satr. Object.keys ["1", "2"] qaytaradi, [1, 2] emas. number imzosi faqat "bu yerga son bilan murojaat qilinadi" degan tur darajasidagi va'da.

2.3 Aniq maydon va imzo birga

Lug'atga bitta aniq maydon qo'shmoqchimiz — valyuta nomi:

ts
type PriceList = {
  [dish: string]: number;
  currency: string;
};

Kompilyator:

text
TS2411: Property 'currency' of type 'string' is not assignable to 'string' index type 'number'.

Tarjimasi: "string turidagi currency xususiyatini string kalitli imzoning number turiga berib bo'lmaydi". Mantiqi oddiy: imzo "har qanday satr kalitning qiymati — son" deydi. "currency" ham satr kalit, demak uning qiymati ham son bo'lishi kerak edi.

Ikki yo'l bor. Qiymat turini kengaytirish (number | string) — lekin unda har bir narxni ishlatishdan oldin tekshirishga to'g'ri keladi. Yaxshisi — ma'lumotni ajratish:

ts
type Menu = {
  currency: string;
  prices: { [dish: string]: number };
};

const menu: Menu = { currency: "so'm", prices: { osh: 35000 } };
console.log(menu.prices["osh"], menu.currency); // 35000 so'm

Lug'at — alohida maydonda, aniq maydonlar — tashqarida. Tur toza, ziddiyat yo'q.

2.4 readonly imzo

Narxlar kun davomida o'zgarmasligi kerak bo'lsa, imzo oldiga readonly qo'yiladi:

ts
const prices: { readonly [dish: string]: number } = { osh: 35000 };
prices["osh"] = 30000;

Kompilyator:

text
TS2542: Index signature in type '{ readonly [dish: string]: number; }' only permits reading.

Bu Massiv va tuple turlari darsidagi readonly string[] xabarining aynan o'zi: "bu turdagi indeks imzosi faqat o'qishga ruxsat beradi".

Tekshirib ko'ring: type Stock = { [dish: string]: number; total: number } — xato beradimi? Nega?

Javob

Xato bermaydi. total — satr kalit, qiymati number, imzo esa "har qiymat — son" deydi. Talab bajarildi. Muammo faqat aniq maydonning turi imzo turiga mos kelmaganda chiqadi (currency: string kabi).

3. Yashirin undefined

3.1 TypeScript ushlamaydigan tuzoq

«Nega bu kerak?» bo'limidagi xatoni TypeScript'da takrorlaymiz:

ts
const prices: { [dish: string]: number } = { osh: 35000 };
const price = prices["lag'mon"];
console.log(price.toLocaleString("ru-RU"));

tsc — xatosiz. Hover'da const price: number. Node esa:

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

Bu Massiv va tuple turlari darsidagi prices[5] tuzog'ining o'zi. Index signature "har qanday satr kalitning qiymati — son" deydi. TypeScript buni so'zma-so'z tushunadi: "lag'mon" kaliti ham bor va son deb hisoblaydi. Haqiqatda esa lug'atda bunday kalit yo'q.

3.2 noUncheckedIndexedAccess lug'atda ham ishlaydi

O'sha bayroq bu yerda ham yordam beradi:

ts
// @ts-options: {"noUncheckedIndexedAccess": true}
const prices: { [dish: string]: number } = { osh: 35000 };
const price = prices["lag'mon"];
console.log(price.toLocaleString("ru-RU"));

Kompilyator:

text
TS18048: 'price' is possibly 'undefined'.

Tarjimasi: "price undefined bo'lishi mumkin". TypeScript aytyapti: "bu kalit lug'atda bo'lmasligi mumkin, avval tekshiring". Endi prices[…] ning turi — number | undefined. Tuzatish — null va undefined darsidagi usullar: ?? bilan standart qiymat yoki tekshiruv.

ts
// @ts-options: {"noUncheckedIndexedAccess": true}
const prices: { [dish: string]: number } = { osh: 35000 };

function priceOf(dish: string): number {
  const price = prices[dish];
  if (price === undefined) {
    throw new RangeError(`Menyuda yo'q: ${dish}`);
  }
  return price;
}

console.log(priceOf("osh")); // 35000
console.log(prices["lag'mon"] ?? 0); // 0

Mashqlar papkangizdagi tsconfig.json da bu bayroq yoqilgan. Lug'at bilan ishlaganda u ayniqsa foydali: lug'atda kalit bo'lmasligi — kundalik holat.

3.3 Nuqta bilan murojaat: noPropertyAccessFromIndexSignature

Index signature'li obyektga nuqta bilan ham murojaat qilish mumkin: prices.osh. Bu xavfli ko'rinishi mumkin: prices.osh "aniq maydon" kabi o'qiladi, lekin u lug'atdan olinmoqda. Buni taqiqlaydigan bayroq bor:

ts
// @ts-options: {"noPropertyAccessFromIndexSignature": true}
const prices: { [dish: string]: number } = { osh: 35000 };
console.log(prices.osh);

Kompilyator:

text
TS4111: Property 'osh' comes from an index signature, so it must be accessed with ['osh'].

Tarjimasi: "osh xususiyati index signature'dan keladi, shuning uchun unga ['osh'] bilan murojaat qilish kerak". Kvadrat qavs o'quvchiga signal beradi: "bu kalit bo'lmasligi mumkin".

Bu bayroq — uslub masalasi. vazifalar loyihasida o'lchanganda u 13 ta joyni ko'rsatdi (xom.versiya kabi). Hammasi to'g'ri ishlaydigan kod edi, shuning uchun bayroq yoqilmadi. Batafsil — Strict bayroqlar oilasi darsida.

Tekshirib ko'ring: noUncheckedIndexedAccess yoqilgan. const p = prices["osh"] ?? 0; — p ning turi qanday?

Javob

number. prices["osh"] — number | undefined, ?? esa undefined bo'lsa o'ng tomonni (0) oladi. Natijada undefined qolmaydi. Bu lug'atdan qiymat olishning eng qisqa xavfsiz yo'li.

4. Record<K, V>

4.1 Index signature'ning qisqa yozuvi

TypeScript'da tayyor tur bor — Record ("yozuv"). Burchak qavs ichida ikki tur: kalit turi va qiymat turi:

ts
const prices: Record<string, number> = { osh: 35000, manti: 30000 };
console.log(prices["manti"]); // 30000

Record<string, number> — { [x: string]: number } ning aynan o'zi, faqat qisqaroq. Array<number> kabi bu ham generic yozuv (Massiv va tuple turlari darsida o'qishni o'rgangansiz). Record qanday yasalgani — Utility types va Mapped types darslarida.

4.2 Aniq kalitlar: hammasi majburiy

Record ning haqiqiy kuchi — kalit o'rnida literal union berish. Index signature'da bu taqiqlangan edi (TS1337), Record da esa mumkin:

ts
type DishName = "osh" | "manti" | "lagmon";

const prices: Record<DishName, number> = {
  osh: 35000,
  manti: 30000,
  lagmon: 28000,
};
console.log(prices.lagmon); // 28000

Bu endi lug'at emas — uchta aniq maydonli obyekt. Record<DishName, number> — "DishName dagi har bir nomga son". Bitta taomni unutsak:

ts
type DishName = "osh" | "manti" | "lagmon";
const prices: Record<DishName, number> = { osh: 35000, manti: 30000 };

Kompilyator:

text
TS2741: Property 'lagmon' is missing in type '{ osh: number; manti: number; }' but required in type 'Record<DishName, number>'.

Tarjimasi: "lagmon xususiyati { osh: number; manti: number; } turida yo'q, lekin Record<DishName, number> turida majburiy". Ya'ni: "uchta taom va'da qilgansiz, ikkitasini berdingiz — lag'mon narxi qani?"

Ortiqcha taom qo'shsak:

ts
type DishName = "osh" | "manti" | "lagmon";
const prices: Record<DishName, number> = {
  osh: 35000,
  manti: 30000,
  lagmon: 28000,
  somsa: 8000,
};

Kompilyator:

text
TS2353: Object literal may only specify known properties, and 'somsa' does not exist in type 'Record<DishName, number>'.

Tarjimasi: "Obyekt literalida faqat ma'lum xususiyatlar bo'lishi mumkin, somsa esa Record<DishName, number> turida yo'q". Bu Obyekt turlari darsidagi ortiqcha xususiyat tekshiruvi.

Ikkala xato ham juda qimmatli. Ertaga DishName ga "somsa" qo'shilsa, narxlar obyekti darhol TS2741 beradi: "somsa narxini unutdingiz". Union va unga bog'liq lug'at doim bir-biriga mos qoladi.

Indeksdagi farqqa ham qarang. Kalitlar aniq bo'lgani uchun prices.lagmon turi — number, noUncheckedIndexedAccess yoqilgan bo'lsa ham. Kalit bor ekani turda yozilgan, undefined bo'lishi mumkin emas.

4.3 Ba'zi kalitlar bo'lmasligi mumkin

Ombordagi qoldiq: hamma taom ham hisoblanmagan bo'lishi mumkin. Unda kalitlar aniq, lekin har biri ixtiyoriy. Buning uchun Record ni Partial ("qisman") ga o'raymiz:

ts
type DishName = "osh" | "manti" | "lagmon";
const stock: Partial<Record<DishName, number>> = { osh: 12 };
console.log(stock.osh, stock.manti); // 12 undefined

Partial<…> — ichidagi turning har maydonini ixtiyoriy (?) qiladigan tayyor tur. Kompilyator bu yozuvni ichkaridan tashqariga qarab ochadi:

Qadam Nima bo'ldi? Natija
1 DishName ning har nomi kalit bo'ldi Record<DishName, number>
2 Ochilgan ko'rinishi { osh: number; manti: number; lagmon: number }
3 Partial har maydonga ? qo'ydi { osh?: number; manti?: number; lagmon?: number }

Shuning uchun hover'da stock.manti — number | undefined, bayroqsiz ham. Endi tekshiruvsiz songa berib bo'lmaydi:

ts
type DishName = "osh" | "manti" | "lagmon";
const stock: Partial<Record<DishName, number>> = { osh: 12 };
const left: number = stock.manti;

Kompilyator:

text
TS2322: Type 'number | undefined' is not assignable to type 'number'.
  Type 'undefined' is not assignable to type 'number'.

Tarjimasi: "number | undefined turini number ga berib bo'lmaydi. undefined ni number ga berib bo'lmaydi". Ikkinchi qator sababni aniqlaydi: muammo — union'ning undefined qismida. TypeScript aytyapti: "manti hali sanalmagan bo'lishi mumkin".

Bu juftlik bilan uchta holat yopiladi. Record<Union, V> — hamma kalit bor. Partial<Record<Union, V>> — kalitlar ma'lum, lekin bo'lmasligi mumkin. Record<string, V> — kalitlar noma'lum. Partial va boshqa tayyor turlar — Utility types darsida.

4.4 vazifalar dagi Record

vazifalar loyihasida filtrlarning bo'sh holat matnlari aynan shunday yozilgan (render.ts, TypeScript va DOM qadamida):

ts
// Record<Filtr, string>: har filtrga matn — biri unutilsa, tsc aytadi
const boshMatnlar: Readonly<Record<Filtr, string>> = Object.freeze({
  hammasi: "Hozircha vazifa yo'q. Birinchisini qo'shing!",
  faol: "Faol vazifa yo'q.",
  bajarilgan: "Bajarilgan vazifa hali yo'q.",
});

Filtr — "hammasi" | "faol" | "bajarilgan". Kimdir yangi filtr qo'shsa, bu obyekt TS2741 bilan "matnini yozing" deydi. Readonly<…> — obyektning hamma maydonini readonly qiladigan tayyor tur (Utility types darsida). Object.freeze esa ishlash paytidagi himoya (Obyektni himoyalash).

Tekshirib ko'ring: type Size = "S" | "M" | "L"; const extra: Record<Size, number> = { S: 0, M: 3000 }; — nima bo'ladi? { S: 0, M: 3000, L: 5000 } chi?

Javob

Birinchisi — TS2741: Property 'L' is missing…: Record da union'ning har bir a'zosi majburiy kalit. Ikkinchisi — xatosiz: uchala o'lcham bor, ortiqcha kalit yo'q.

5. Obyekt aylanganda kalitlar string

5.1 Object.keys nega string[]?

Kalitlari aniq obyektni aylanib chiqamiz:

ts
const prices = { osh: 35000, manti: 30000 };
const names = Object.keys(prices);
const pairs = Object.entries(prices);
console.log(names, pairs.length); // [ 'osh', 'manti' ] 2

Hover'da names: string[], pairs: [string, number][] — ("osh" | "manti")[] emas. Nega TypeScript kalitlarni "unutdi"?

Sababi — Strukturaviy tiplash. { osh: number; manti: number } turiga ko'proq maydonli obyekt ham mos keladi. Funksiya shu turdagi parametrni olsa, haqiqatda unga { osh, manti, somsa } kelishi mumkin. Object.keys esa haqiqiy kalitlarni qaytaradi, "somsa" ni ham. Shuning uchun TypeScript halol javob beradi: "satrlar".

5.2 string kalit bilan aniq Record ga murojaat

Shu sabab aniq kalitli obyektga istalgan satr bilan murojaat qilib bo'lmaydi:

ts
type DishName = "osh" | "manti";
const prices: Record<DishName, number> = { osh: 35000, manti: 30000 };
function priceOf(name: string): number {
  return prices[name];
}

Kompilyator:

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

Tarjimasi: "string turidagi ifoda bilan Record<DishName, number> ga indeks berib bo'lmaydi, shuning uchun element yashirincha any bo'ldi. Bu turda string parametrli index signature topilmadi". Oddiyroq qilib: "istalgan satr kalit bo'lishi mumkin, lekin bu obyektda faqat ikki kalit bor".

Tuzatish — satrni avval toraytirish. Custom type guard darsidagi tur predikati shu yerda kerak bo'ladi:

ts
type DishName = "osh" | "manti";
const prices: Record<DishName, number> = { osh: 35000, manti: 30000 };

function isDishName(name: string): name is DishName {
  return Object.hasOwn(prices, name);
}

function priceOf(name: string): number {
  if (!isDishName(name)) {
    throw new RangeError(`Menyuda yo'q: ${name}`);
  }
  return prices[name];
}
console.log(priceOf("manti")); // 30000

Object.hasOwn(obyekt, kalit) — "bu kalit obyektning o'zida bormi?" (Object.* metodlari). Nega name in prices emas? Buni keyingi bo'limdagi tuzoq ko'rsatadi.

isDishName true qaytargan shoxda name ning turi string dan DishName ga torayadi. Shuning uchun prices[name] endi xatosiz va natija — number. Tekshiruv ham, tur ham bir joyda: noto'g'ri nom ishlash paytida RangeError beradi, to'g'risi esa tur darajasida tasdiqlanadi.

Tekshirib ko'ring: const names = Object.keys(prices) as DishName[]; — bu qator nimani va'da qiladi va qachon yolg'on bo'lib chiqadi?

Javob

as — "men bilaman, ishon" degani: kalitlar faqat DishName lardan iborat deb va'da beradi. prices haqiqatan shu obyekt bo'lsa — rost. Lekin funksiyaga ortiqcha maydonli obyekt kelsa (strukturaviy tiplash ruxsat beradi), massivda DishName bo'lmagan kalit paydo bo'ladi va tur yolg'on gapiradi. as tekshirmaydi — xavfsiz yo'l tur predikati.

6. Oddiy obyekt lug'atining tuzog'i: meros kalitlari

6.1 "constructor" degan so'z

Kunlik buyurtmalarni sanaymiz: qaysi so'z necha marta. Ro'yxatda tasodifan "constructor" degan yozuv bor (masalan, kimdir izoh maydoniga yozgan):

ts
const words = ["osh", "constructor", "osh"];
const counts: Record<string, number> = {};
for (const w of words) {
  counts[w] = (counts[w] ?? 0) + 1;
}
console.log(counts["osh"]);
console.log(counts["constructor"]);

Konsolda:

text
2
function Object() { [native code] }1

tsc — xatosiz, hamma tur to'g'ri ko'rinadi. Natija esa g'alati: son o'rniga funksiya matni va "1". Sababi — prototiplar. Oddiy {} obyekt Object.prototype dan meros oladi, unda esa constructor, toString, hasOwnProperty kabi xususiyatlar bor. counts["constructor"] bo'sh obyektda ham undefined emas — meros qolgan funksiya. ?? uni "bor qiymat" deb oldi, unga 1 qo'shilib, satr chiqdi.

TypeScript buni ko'rmaydi: Record<string, number> "har qiymat — son" deydi va unga ishonadi. Bu ham "tur — faqat va'da" holati. name in prices ham shu sababli xavfli: "toString" in prices — true.

6.2 Himoya: prototipsiz obyekt yoki Map

Ikki yechim bor:

  • Object.create(null) — prototipi yo'q obyekt, unda meros kalitlar yo'q.
  • Map — kalit-qiymat juftliklari uchun maxsus tuzilma (Map darsi). Unda meros kalitlar umuman yo'q.
ts
const words = ["osh", "constructor", "osh"];
const counts = new Map<string, number>();
for (const w of words) {
  counts.set(w, (counts.get(w) ?? 0) + 1);
}
console.log(counts.get("constructor")); // 1
console.log(counts.get("manti")); // undefined

new Map<string, number>() — bo'sh Map, kalit va qiymat turi burchak qavsda (xuddi Record kabi). Map ning yana bir afzalligi: counts.get(…) turi doim number | undefined, bayroqsiz ham. TypeScript sizni tekshirishga majbur qiladi.

6.3 Qachon nima?

Tanlash uchun ikki savol yetadi:

flowchart TD
  A["Kalit → qiymat"] --> B{"Kalitlar ro'yxati<br/>ma'lummi?"}
  B -- "ha" --> C["Record&lt;Union, V&gt;<br/>hammasi majburiy"]
  B -- "yo'q" --> D{"Kalit<br/>foydalanuvchidanmi?"}
  D -- "ha" --> E["Map&lt;K, V&gt;"]
  D -- "yo'q, JSON" --> F["Record&lt;string, V&gt;<br/>+ bayroq"]
Record<string, V> Map<K, V>
JSON'ga to'g'ridan-to'g'ri ha yo'q (Object.fromEntries kerak)
Kalit turi satr (son ham satrga aylanadi) istalgan: son, obyekt
Meros kalitlar (constructor) bor — tuzoq yo'q
get natijasi V (bayroqsiz) doim V | undefined

Serverdan JSON kelsa yoki unga yozilsa — Record, noUncheckedIndexedAccess bayrog'i bilan. Kalitlar foydalanuvchidan kelsa yoki tez-tez qo'shilib-o'chirilsa — Map. Aniq ro'yxat bo'lsa — Record<Union, V>.

Tekshirib ko'ring: Mehmonlar o'z ismini yozadigan "kim necha marta keldi" hisoblagichi uchun Record<string, number> mi yoki Map<string, number> mi?

Javob

Map. Kalit — foydalanuvchi yozgan matn. "constructor" yoki "toString" kabi ism kirib qolishi mumkin va oddiy obyektda u meros xususiyat bilan to'qnashadi. Map da bunday kalitlar yo'q, get esa doim undefined ni tekshirtiradi.

7. interface va index signature

7.1 Yashirin imzo faqat type da

vazifalar loyihasida .ts ga ko'chishning birinchi qadamida (type va interface qadami) haqiqiy xato chiqqan edi. Uning sababi — shu darsning mavzusi. Kichik misolda takrorlaymiz:

ts
interface Guest {
  name: string;
  people: number;
}
function fieldCount(data: Record<string, unknown>): number {
  return Object.keys(data).length;
}
const guest: Guest = { name: "Malika", people: 2 };
fieldCount(guest);

Kompilyator:

text
TS2345: Argument of type 'Guest' is not assignable to parameter of type 'Record<string, unknown>'.
  Index signature for type 'string' is missing in type 'Guest'.

Tarjimasi: "Guest turidagi argumentni Record<string, unknown> parametriga berib bo'lmaydi: Guest turida string uchun index signature yo'q". Guest ni type bilan yozsangiz, xato yo'qoladi:

ts
type Guest = {
  name: string;
  people: number;
};
function fieldCount(data: Record<string, unknown>): number {
  return Object.keys(data).length;
}
const guest: Guest = { name: "Malika", people: 2 };
console.log(fieldCount(guest)); // 2

Nega shunday? TypeScript type bilan yozilgan obyekt turiga yashirin index signature beradi. Uning hamma kaliti ma'lum, demak "har satr kalit — unknown" degan talab bajariladi. Interfeysga esa bermaydi, chunki u declaration merging bilan keyinroq kengayishi mumkin. Unga kelajakda boshqa turdagi maydon qo'shilishi mumkin, shuning uchun TypeScript oldindan va'da bermaydi.

Bu farq faqat Record<string, …> kabi "istalgan kalit" turlariga berishda chiqadi. Oddiy holatlarda — maydonlarni o'qish, funksiyaga berish — type va interface bir xil ishlaydi.

vazifalar dagi xato aynan shu edi: tekshiruv funksiyasi ma'lumotni Record<string, unknown>[] deb qabul qilardi, interface VazifaMalumoti esa unga mos kelmadi. Kanon qarori — turlar.ts dagi ma'lumot shakllari type bilan yoziladi.

8. Ko'p uchraydigan xatolar

8.1 Index signature'da literal union

{ [dish: "osh" | "manti"]: number } — TS1337. Tuzatish: Record<"osh" | "manti", number>.

8.2 Aniq maydon imzo turiga mos emas

{ [k: string]: number; currency: string } — TS2411. Tuzatish: lug'atni alohida maydonga oling (prices: Record<string, number>).

8.3 Lug'atdan olingan qiymatga ko'r-ko'rona ishonish

prices[name] — number, haqiqatda undefined bo'lishi mumkin. Tuzatish: noUncheckedIndexedAccess yoqing, ?? yoki === undefined tekshiruvi.

8.4 Aniq Record ga string bilan murojaat

prices[name] (name: string) — TS7053. Tuzatish: avval tur predikati bilan toraytiring (isDishName). as DishName yozib qo'yish xatoni yashiradi, lekin tekshirmaydi (Type assertion darsidagi xavf).

8.5 Foydalanuvchi kalitlari oddiy obyektda

"constructor", "toString", "__proto__" kabi kalitlar meros xususiyatlar bilan to'qnashadi. Tuzatish: Map yoki Object.create(null), tekshiruvda in o'rniga Object.hasOwn.

9. Mashqlar

1-mashq (oson): Turni ayting

ts
type Table = 1 | 2 | 3;
const waiters: Record<Table, string> = {
  1: "Sardor",
  2: "Sardor",
  3: "Bobur",
};
const notes: Record<string, string> = {};
console.log(Object.keys(waiters).length); // 3

waiters da nechta majburiy kalit bor: [:3]. Object.keys(waiters) ning turi — string[] mi yoki Table[] mi: [:string]. noUncheckedIndexedAccess yoqilganda notes["5"] ning turi string | …: .

Yechim
  • Uchta: Record<Table, string> — union'ning har a'zosi majburiy kalit. Son kalitlar ham obyektda satrga aylanadi, lekin tur darajasida 1, 2, 3 tekshiriladi.
  • string[]: Object.keys doim satrlar massivini qaytaradi (strukturaviy tiplash tufayli).
  • string | undefined: notes — istalgan kalitli lug'at, bayroq har murojaatga undefined ni qo'shadi.

2-mashq (o'rta): Ish vaqti jadvali

Bu kod hafta kunlari uchun ish vaqtini saqlaydi, lekin uchta muammo bor: yakshanba unutilgan, tur buni aytmaydi va mavjud bo'lmagan kun so'ralganda TypeError chiqadi.

ts
const hours: Record<string, string> = {
  dushanba: "07:00–23:00",
  seshanba: "07:00–23:00",
};
function hoursOf(day: string): string {
  return hours[day].toUpperCase();
}
console.log(hoursOf("yakshanba"));

Konsolda:

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

Vazifa:

  1. type Day = "dushanba" | "seshanba" | "yakshanba" yozing (qisqalik uchun uch kun). hours ni Record<Day, string> qiling — tsc unutilgan kunni ko'rsatsin.
  2. hoursOf(day: Day) — endi noto'g'ri kun nomi chaqiruvning o'zida ushlansin.
  3. Yakshanba — "dam olish".
Yechim

Birinchi qadamdan keyin (yakshanba hali yo'q) tsc shunday deydi: TS2741: Property 'yakshanba' is missing in type '{ dushanba: string; seshanba: string; }' but required in type 'Record<Day, string>'. Tuzatilgan kod:

ts
type Day = "dushanba" | "seshanba" | "yakshanba";

const hours: Record<Day, string> = {
  dushanba: "07:00–23:00",
  seshanba: "07:00–23:00",
  yakshanba: "dam olish",
};

function hoursOf(day: Day): string {
  return hours[day];
}
console.log(hoursOf("yakshanba")); // dam olish

Endi hoursOf("juma") yozsangiz, tsc darhol to'xtatadi: "juma" Day turida yo'q (TS2345). Xato ishlash paytidan yozish paytiga ko'chdi. hours[day] turi string — noUncheckedIndexedAccess bilan ham: kalit Day dan, demak albatta bor.

3-mashq (qiyin): Amaliy tajriba — buyurtma hisobi

Mashq kurs/mashqlar/15/ papkasida (noUncheckedIndexedAccess yoqilgan). 17-lugat/stock.ts faylini yarating:

  • type DishName = "osh" | "manti" | "lagmon" va PRICES: Readonly<Record<DishName, number>> (35 000, 30 000, 28 000).
  • orderTotal(order) — buyurtma: "taom nomi → porsiya soni" lug'ati (Readonly<Record<string, number>>). Menyuda yo'q taom — RangeError. Ishora: Object.entries(order) — [nom, soni] juftliklari.
  • countDishes(names) — kun davomidagi taom nomlari ro'yxatidan "nom → necha marta" Map qaytarsin.

Avval eng sodda variantni yozing — sum += PRICES[name] * qty; — va npx tsc --pretty false ni ishga tushiring. Keyin testlar: summa (bo'sh buyurtma ham), menyuda yo'q taom ("somsa" va "constructor"), sanash.

Yechim

Sodda variantda tsc 7.0.2 shunday dedi:

text
17-lugat/stock.ts(14,12): error TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'Readonly<Record<DishName, number>>'.
  No index signature with a parameter of type 'string' was found on type 'Readonly<Record<DishName, number>>'.

«string kalit bilan aniq Record ga murojaat» bo'limidagi holat: Object.entries kalitni string deb beradi, PRICES esa faqat uch kalitni taniydi. Toraytirish kerak:

ts
// 17-lugat/stock.ts — «Bahor» narxlari va buyurtma hisobi
export type DishName = "osh" | "manti" | "lagmon";

export const PRICES: Readonly<Record<DishName, number>> = {
  osh: 35000,
  manti: 30000,
  lagmon: 28000,
};

// Tur predikati: matn menyudagi taom nomimi?
export function isDishName(name: string): name is DishName {
  return Object.hasOwn(PRICES, name);
}

// buyurtma: taom nomi → porsiya soni
export function orderTotal(
  order: Readonly<Record<string, number>>,
): number {
  let sum = 0;
  for (const [name, qty] of Object.entries(order)) {
    if (!isDishName(name)) {
      throw new RangeError(`Menyuda yo'q: ${name}`);
    }
    sum += PRICES[name] * qty;
  }
  return sum;
}

// Kun davomidagi buyurtmalar: qaysi taom necha marta
export function countDishes(
  names: readonly string[],
): Map<string, number> {
  const counts = new Map<string, number>();
  for (const name of names) {
    counts.set(name, (counts.get(name) ?? 0) + 1);
  }
  return counts;
}
ts
// 17-lugat/stock.test.ts
import { test } from "node:test";
import assert from "node:assert/strict";
import { orderTotal, countDishes, isDishName } from "./stock.ts";

test("orderTotal: 2 osh va 1 manti", () => {
  assert.equal(orderTotal({ osh: 2, manti: 1 }), 100000);
  assert.equal(orderTotal({}), 0);
});

test("orderTotal: menyuda yo'q taom — RangeError", () => {
  assert.throws(() => orderTotal({ somsa: 1 }), RangeError);
  assert.throws(() => orderTotal({ constructor: 1 }), RangeError);
});

test("isDishName: faqat o'z kalitlari", () => {
  assert.equal(isDishName("lagmon"), true);
  assert.equal(isDishName("toString"), false);
});

test("countDishes: Map bilan sanash", () => {
  const counts = countDishes(["osh", "constructor", "osh"]);
  assert.equal(counts.get("osh"), 2);
  assert.equal(counts.get("constructor"), 1);
  assert.equal(counts.get("manti"), undefined);
});
  • Object.entries(order) dagi qty — number, undefined emas: entries faqat mavjud juftliklarni beradi.
  • isDishName("toString") — false, chunki Object.hasOwn meros xususiyatlarni hisoblamaydi. "toString" in PRICES esa true bo'lardi va PRICES["toString"] * qty — NaN.
  • countDishes — Map: "constructor" oddiy so'z kabi sanaldi.

npx tsc --pretty false — chiqishsiz. node --test 17-lugat/stock.test.ts bizda (Node 24.21.0) shunday chiqdi:

text
✔ orderTotal: 2 osh va 1 manti (3.4322ms)
✔ orderTotal: menyuda yo'q taom — RangeError (1.6266ms)
✔ isDishName: faqat o'z kalitlari (0.4582ms)
✔ countDishes: Map bilan sanash (0.7456ms)
ℹ tests 4
ℹ suites 0
ℹ pass 4
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 369.2309

XATOLAR.md ga qo'shing: TS1337, TS2411, TS2353, TS7053, TS4111 va interface uchun "Index signature … is missing". Keyin kurs/mashqlar papkasidan:

bash
git add 15/17-lugat 15/XATOLAR.md
git commit -m "15/17: stock.ts — Record, tur predikati va Map"

10. Real ishda

  • Tarjimalar (i18n): Record<Lang, string> yoki Record<string, string> — "kalit → matn". Aniq tillar ro'yxati bilan Record<"uz" | "ru" | "en", string> har tilni unutmaslikni kafolatlaydi.
  • Holat → matn/rang jadvallari: vazifalar dagi boshMatnlar, PANEL_RANGLARI kabi — enum va switch o'rniga ko'p ishlatiladi. Keyingi darslarda yana ko'rasiz.
  • HTTP sarlavhalari: headers: Record<string, string> — vazifalar dagi api.ts da ham shunday.
  • Kesh va hisoblagichlar — Map: kalitlar dinamik, tez-tez qo'shiladi va o'chiriladi.
  • Intervyu: "Index signature nima?", "Record va Map farqi?", "Object.keys nega string[] qaytaradi?" — tez-tez so'raladi.

Xulosa

  • Index signature { [k: string]: V } — kalitlari noma'lum lug'at. Kalit turi — string, number, symbol yoki shablon; aniq maydonlar imzo turiga mos bo'lishi kerak.
  • Record<string, V> — o'sha narsaning qisqa yozuvi. Record<Union, V> esa union'ning har a'zosini majburiy kalit qiladi.
  • Lug'atdan olingan qiymat turi undefined ni hisobga olmaydi. noUncheckedIndexedAccess buni tuzatadi, ?? va tekshiruv bilan ishlang.
  • Object.keys/entries kalitlarni string deb beradi. Aniq Record ga murojaatdan oldin satrni tur predikati bilan toraytiring.
  • Oddiy obyekt lug'atida meros kalitlar (constructor) bor. Foydalanuvchi kalitlari uchun Map yoki Object.hasOwn.
  • interface ga yashirin index signature berilmaydi — Record<string, unknown> ga mos kelishi kerak bo'lsa, type yozing.

Keyingi dars: Klasslar TypeScript'da — public/private/protected, readonly, abstrakt klass va implements. vazifalar dagi Vazifa klassi .ts ga ko'chadi.

Manbalar

  • TypeScript Handbook: "Object Types" (Index Signatures) — typescriptlang.org/docs/handbook/2/objects.html#index-signatures
  • TypeScript Handbook: "Utility Types" (Record<Keys, Type>) — typescriptlang.org/docs/handbook/utility-types.html#recordkeys-type
  • TSConfig: noUncheckedIndexedAccess, noPropertyAccessFromIndexSignature — typescriptlang.org/tsconfig
  • MDN: Object.hasOwn(), Map — developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
TypeScript index signature va Record: kalitlari noma'lum obyektlar — IlmHamroh