IlmHamroh
JavaScript Full-stack/15-qism. TypeScript30/47-dars19 daqiqa
Mundarija (29)

TypeScript template literal types: satr turlarini shablon bilan yasash

Qisqacha: Template literal type — JavaScript'dagi shablon satr (`on${name}`), lekin turlar bilan: `order:${OrderEvent}` — "order: bilan boshlanib, hodisa nomi bilan tugaydigan satr". Union qo'yilsa, hamma kombinatsiyalar chiqadi. Capitalize, Uppercase kabi tayyor turlar harflarni o'zgartiradi, infer esa satrni bo'laklarga ajratadi. Hammasi faqat kompilyatsiyada ishlaydi: ishlash paytidagi satrni siz o'zingiz yasaysiz va tekshirasiz.

Bu darsda

  • `order:${OrderEvent}` kabi tur yozasiz va kompilyator undan qanday union yasashini ko'rsata olasiz.
  • Uppercase, Lowercase, Capitalize, Uncapitalize ni ishlatasiz.
  • Mapped type bilan onCreated, onReady kabi kalitlar yasaysiz.
  • `${number}:${number}` kabi naqsh turlari nimani tekshirishini va nimani tekshirmasligini bilasiz.
  • infer bilan satr turini bo'laklarga ajratasiz: "/menu/:id" → "id".

Oldin bilishingiz kerak: infer kalit so'zi, Mapped types, Shablon satrlar.

1. Nega bu kerak?

O'tgan darsda infer bilan turning ichiga kirdik. Bugun yana bir "ichki dunyo" — satr turlari. TypeScript satrning ichini ham ko'ra oladi: qaysi so'z bilan boshlanadi, nimadan iborat.

«Bahor» oshxona ekrani hodisalar bilan ishlaydi: buyurtma yaratildi, tayyor bo'ldi, mijozga berildi. Har hodisaning nomi — satr: "order:created", "order:ready", "order:served". Sardor hodisa yuboradigan funksiya yozdi:

ts
const listeners = new Map<string, () => void>();
listeners.set("order:ready", () => {
  console.log("Ofitsiant chaqirildi");
});

function emit(name: string) {
  listeners.get(name)?.();
}

emit("order:ready"); // Ofitsiant chaqirildi
emit("order:redy");

Ikkinchi chaqiruvda harf tushib qolgan. JavaScript ham, string turi ham indamadi — hodisa shunchaki hech kimga yetmadi. Ofitsiant chaqirilmadi, osh soviyapti.

Sardor turni literal union qildi: "order:created" | "order:ready" | "order:served". Xato ushlanadi. Lekin bir hafta o'tib stollar uchun ham hodisalar kerak bo'ldi: "table:free", "table:busy". Keyin har hodisa uchun ishlovchi nomi: "onCreated", "onReady"… Har ro'yxat qo'lda yoziladi va mapped types darsidagi takrorlanish muammosi yana qaytadi: yangi hodisa — uch joyda tahrir.

Template literal type nomlarni bitta manbadan yasaydi:

ts
type OrderEvent = "created" | "ready" | "served";
type EventName = `order:${OrderEvent}`;

const ev: EventName = "order:cancelled";
const ev2: EventName = "order:Ready";

Kompilyator:

text
TS2322: Type '"order:cancelled"' is not assignable to type '"order:created" | "order:ready" | "order:served"'.
TS2820: Type '"order:Ready"' is not assignable to type '"order:created" | "order:ready" | "order:served"'. Did you mean '"order:ready"'?
  • Birinchisi: "order:cancelled" — ro'yxatda yo'q hodisa.
  • Ikkinchisi yangi kod — TS2820: xuddi shu xabar, oxirida esa ""order:ready" ni nazarda tutdingizmi?". Kompilyator katta R ni sezdi va o'xshash variantni taklif qildi.

E'tibor bering: xabarda EventName emas, uning hisoblangan ko'rinishi — uchta satrning union'i. OrderEvent ga "cancelled" qo'shsangiz, EventName o'zi to'rtta bo'ladi.

2. Template literal type: shablondan union

2.1 Yozuv va hisoblash

Template literal type (shablon satr turi) — backtick ichida yozilgan tur: oddiy matn va ${…} ichida boshqa turlar. Yozuvi shablon satrlar bilan bir xil, faqat ${…} ichida qiymat emas, tur turadi.

ts
type OrderEvent = "created" | "ready" | "served";
type EventName = `order:${OrderEvent}`;

const ev: EventName = "order:ready";
console.log(ev); // order:ready

Kompilyator EventName ni shunday ochadi:

Qadam Nima qiladi Natija
1 ${…} ichidagi union a'zolarini oladi "created", "ready", "served"
2 har biriga shablonni qo'llaydi "order:created", "order:ready", "order:served"
3 natijalarni union'ga yig'adi uch satrli union

Bu conditional type'dagi taqsimlanish ga o'xshaydi: union a'zolari bittalab ishlanadi.

Muhimi — natija oddiy literal union. Undan keyin EventName bilan qo'lda yozilgan "order:created" | "order:ready" | "order:served" o'rtasida hech qanday farq yo'q: switch bilan toraytirish, to'liqlik tekshiruvi, Record<EventName, …> — hammasi ishlaydi. Template literal type yangi turdagi tur emas, union yasashning qisqa yo'li.

2.2 Ikki union — hamma juftliklar

${…} ikkita bo'lsa, kompilyator hamma juftliklarni yasaydi. «Bahor» menyusida porsiya kalitlari — taom va o'lcham:

ts
type Dish = "osh" | "manti";
type Size = "kichik" | "katta";
type PortionKey = `${Dish}-${Size}`;

const key: PortionKey = "manti-katta";
console.log(key); // manti-katta

Hover: type PortionKey = "osh-kichik" | "osh-katta" | "manti-kichik" | "manti-katta". Ikki × ikki = to'rt. Rasmda:

flowchart LR
  D1["osh"] --> K1["osh-kichik"]
  D1 --> K2["osh-katta"]
  D2["manti"] --> K3["manti-kichik"]
  D2 --> K4["manti-katta"]
  S1["kichik"] --> K1
  S1 --> K3
  S2["katta"] --> K2
  S2 --> K4

Kunlik o'xshatish: kiyim do'konidagi yorliqlar. Uch xil rang va to'rt xil o'lcham bo'lsa — o'n ikki xil yorliq bosiladi, har biri alohida.

Diqqat: juftliklar soni ko'paytma bilan o'sadi. 10 ta taom × 3 o'lcham × 4 qo'shimcha = 120 ta satr. Ikki xonali raqam (${0|1|…|9}${0|1|…|9}) — 100 ta. Kombinatsiyalar 100 000 ga yetsa, kompilyator hisoblashdan bosh tortadi va xato beradi (TS2590). Bu chegarani Rekursiv turlar va tur murakkabligi chegaralari darsida ko'ramiz.

2.3 Qachon template literal, qachon oddiy union

Template literal type'ni qoida bo'lganda yozing: "hamma buyurtma hodisalari order: bilan boshlanadi", "har hodisaning ishlovchisi on + hodisa nomi". Qoida turga yozilsa, uni buzib bo'lmaydi va yangi a'zo qo'shish bir joyda bo'ladi.

Qoida bo'lmasa — oddiy union yaxshiroq. Masalan, to'lov usullari: "naqd" | "karta" | "Click" | "Payme". Ularni shablon bilan yig'ishning ma'nosi yo'q: umumiy qism yo'q, har biri o'z nomi. Shablonga majburlash faqat o'qishni qiyinlashtiradi.

Tekshirib ko'ring: type Table = `${1 | 2 | 3}-stol` — nechta satr va qaysilari?

Javob

Uchta: "1-stol" | "2-stol" | "3-stol". Shablon ichida son literal ham ishlaydi — u matnga aylanadi, xuddi JavaScript'dagi `${1}-stol` kabi.

3. Harflarni o'zgartiradigan tayyor turlar

TypeScript'da satr turining harflarini o'zgartiradigan to'rtta tayyor tur bor:

Tur Nima qiladi Misol
Uppercase<S> hammasi katta "osh" → "OSH"
Lowercase<S> hammasi kichik "OSH" → "osh"
Capitalize<S> birinchi harf katta "ready" → "Ready"
Uncapitalize<S> birinchi harf kichik "Osh" → "osh"

Ular lib.es5.d.ts da odatdagidek yozilmagan — type Capitalize<S extends string> = intrinsic;. intrinsic (ichki) — "bu turni kompilyatorning o'zi hisoblaydi" degani. Harflarni o'zgartirishni mapped yoki conditional type bilan yozib bo'lmaydi, shuning uchun u kompilyatorga qurilgan.

ts
type OrderEvent = "created" | "ready";
type HandlerName = `on${Capitalize<OrderEvent>}`;

const h: HandlerName = "onReady";
const level: Capitalize<"o'rta"> = "O'rta";
console.log(h, level); // onReady O'rta

HandlerName — "onCreated" | "onReady". Avval Capitalize union'ning har a'zosiga qo'llandi, keyin shablon. Capitalize<"o'rta"> — "O'rta": birinchi belgi o katta bo'ldi, apostrof joyida qoldi.

Bu turlar faqat kompilyatsiyada ishlaydi. Ishlash paytida ularning hech qanday izi yo'q: "ready" ni "Ready" ga aylantirish uchun JavaScript'da charAt(0).toUpperCase() kabi kodni baribir o'zingiz yozasiz. Tur faqat "natija shunday bo'lishi kerak" deb aytadi — buni «Qiymatlar dunyosida» bo'limida batafsil ko'ramiz.

Yana bir nozik joy: Capitalize faqat birinchi belgini o'zgartiradi. "ko'k choy" → "Ko'k choy", "Ko'k Choy" emas. Har so'zni katta harf bilan boshlaydigan tayyor tur yo'q.

Turlarga faqat satr beriladi:

ts
type Bad = Capitalize<35000>;

Kompilyator:

text
TS2344: Type 'number' does not satisfy the constraint 'string'.

"number turi string cheklovini qanoatlantirmaydi". Capitalize<S extends string> — generic cheklov. Xabarda 35000 emas, number turibdi — kompilyator cheklovni tekshirishda son literalini umumiy number deb ko'rsatdi.

4. Mapped type bilan: onCreated, onReady

Mapped types darsida kalitni as bilan qayta nomlagan edik va shablon yozuvini "keyinroq" deb qoldirgandik. Mana o'sha joy:

ts
type KitchenEvents = { created: number; ready: string };

type Handlers<T> = {
  [K in keyof T as `on${Capitalize<K & string>}`]: (
    value: T[K],
  ) => void;
};

const handlers: Handlers<KitchenEvents> = {
  onCreated: (table) => console.log(`${table}-stol: yangi buyurtma`),
  onReady: (dish) => console.log(`${dish} tayyor`),
};
handlers.onReady("Osh"); // Osh tayyor
K Yangi kalit Qiymat turi
"created" "onCreated" (value: number) => void
"ready" "onReady" (value: string) => void

KitchenEvents — "hodisa → u bilan keladigan ma'lumot" jadvali. Ishlovchilar obyekti undan hisoblandi: kalit nomlari ham, parametr turlari ham. onCreated ichida table — number, onReady ichida dish — string. Yangi hodisa qo'shilsa, handlers da uning ishlovchisi yo'qligini tsc darhol aytadi.

K & string — mapped types darsidagi qoida: kalit symbol bo'lishi ham mumkin, uni shablonga qo'yib bo'lmaydi.

Tekshirib ko'ring: KitchenEvents ga served: number qo'shildi, handlers esa o'zgarmadi. tsc nima deydi?

Javob

TS2741 — "onServed xususiyati yo'q, lekin Handlers<KitchenEvents> turida majburiy". Yangi hodisa kalitdan ishlovchi nomi yasaldi va u majburiy maydonga aylandi. Ishlovchini yozmaguningizcha kod tekshiruvdan o'tmaydi — unutilgan hodisa bo'lmaydi.

5. Naqsh turlari: ${number} va ${string}

5.1 "Shu shakldagi istalgan satr"

${…} ichiga aniq satr emas, umumiy tur qo'ysa ham bo'ladi. Bron vaqti — "son, ikki nuqta, son":

ts
type Time = `${number}:${number}`;

const ok: Time = "19:30";
const bad: Time = "19.30";

Kompilyator:

text
TS2322: Type '"19.30"' is not assignable to type '`${number}:${number}`'.

Tarjimasi: ""19.30" turini `${number}:${number}` turiga berib bo'lmaydi" — nuqta bor, ikki nuqta yo'q. Bunday tur union emas — u naqsh (pattern): cheksiz ko'p satrlarga mos keladi. ${number} — "son bo'lib o'qiladigan matn", ${string} — "istalgan matn" (bo'sh ham).

${string} prefiks yoki qo'shimcha qoidasi uchun qulay. «Bahor» jurnaliga (log) har qanday buyurtma hodisasi yozilishi mumkin — hatto hali OrderEvent da yo'q yangisi ham. Faqat order: bilan boshlanishi shart:

ts
type AnyOrderEvent = `order:${string}`;

function logOrder(name: AnyOrderEvent): string {
  return `[jurnal] ${name}`;
}
console.log(logOrder("order:refund")); // [jurnal] order:refund

"order:refund" — OrderEvent da yo'q, lekin naqshga mos. logOrder("table:free") esa tsc dan o'tmaydi: prefiks boshqa. Aniq union — "faqat shu ro'yxat", naqsh — "shu qoidaga mos istalgani". Qaysi biri kerakligini vaziyat belgilaydi: hodisa ishlovchisi aniq ro'yxatni talab qiladi (har biriga kod yozilgan), jurnal esa qoidaning o'zi bilan kifoyalanadi.

5.2 ${number} aslida nimani qabul qiladi?

Bu yerda ehtiyot bo'ling. TypeScript 7.0.2 da tekshirib ko'rdik:

Satr ${number} ga mosmi? Nega
"35000", "-5.5" ha oddiy son
"1e3", "0x1F", "5." ha JavaScript ularni son deb o'qiydi
" 5" ha boshdagi bo'shliq ham
"", "35 000", "NaN", "Infinity" yo'q —

Ya'ni ${number} — "JavaScript Number() bilan son sifatida o'qiy oladigan matn", "chiroyli raqam" emas. "0x1F" — o'n oltilik son, Number("0x1F") — 31. Turdan o'tdi, lekin «Bahor» narxi sifatida ma'nosiz.

Tekshirib ko'ring: type Price = `${number} so'm` turiga "35000 so'm", "35 000 so'm" va "so'm" dan qaysilari mos?

Javob

Faqat "35000 so'm". "35 000 so'm" da ${number} o'rniga "35 000" tushadi — bo'shliqli raqam son emas. "so'm" da esa ${number} o'rniga bo'sh satr qoladi, bo'sh satr ham son emas. (Shablondagi so'm oldidagi bo'shliq ham hisobga olinadi.)

6. infer bilan satrni bo'lish

O'tgan darsdagi qolip g'oyasi satrlarda ham ishlaydi. Qolip — shablon, bo'sh joylar — infer:

ts
type Split<T> = T extends `${infer Dish}-${infer Size}`
  ? [dish: Dish, size: Size]
  : never;

const parts: Split<"osh-katta"> = ["osh", "katta"];
console.log(parts[1]); // katta
Qadam Nima qiladi Natija
1 "osh-katta" ni ${…}-${…} qolipiga soladi - dan oldin va keyin
2 birinchi infer — birinchi - gacha Dish = "osh"
3 ikkinchi infer — qolgan hammasi Size = "katta"

Uch bo'lakli satrda ("osh-katta-achchiq") natija — ["osh", "katta-achchiq"]. Birinchi infer imkon qadar qisqa bo'lakni oladi (birinchi - gacha), oxirgisi — qolganini.

Yo'l (route) parametri — veb-ilovalarda eng mashhur misol:

ts
type ParamOf<T> = T extends `${string}:${infer P}` ? P : never;

const p: ParamOf<"/menu/:id"> = "id";
console.log(p); // id

${string} — "nima bo'lsa ham" (bu yerda /menu/), : dan keyingisi P bo'ladi. Ikki va undan ko'p parametr ("/menu/:dishId/size/:size") uchun tur o'z-o'zini chaqirishi kerak — buni Rekursiv turlar darsida yozamiz.

Taqsimlanish bu yerda ham ishlaydi — prefiksni olib tashlash:

ts
type WithoutPrefix<T> = T extends `order:${infer Rest}`
  ? Rest
  : never;
type All = "order:created" | "order:ready" | "table:free";

const e: WithoutPrefix<All> = "ready";
console.log(e); // ready

Kompilyator All ni a'zolarga bo'lib, har birini qolipga soladi:

A'zo order:${infer Rest} ga tushdimi? Natija
"order:created" ha, Rest = "created" "created"
"order:ready" ha, Rest = "ready" "ready"
"table:free" yo'q never — o'chadi

Yig'indi: "created" | "ready".

Tekshirib ko'ring: Split<"osh"> (defissiz) nima beradi? ParamOf<"/menu"> chi?

Javob

Ikkalasi ham never. "osh" da - yo'q — ${infer Dish}-${infer Size} qolipiga tushmaydi. "/menu" da : yo'q — parametr qolipiga tushmaydi. O'tgan darsdagi "jim never" qoidasi satrlarda ham amal qiladi: qolip mos kelmasa, xato emas — : shoxi.

7. Qiymatlar dunyosida: shablon satr qaysi tur oladi

Turlar bilan ishlash bir narsa, haqiqiy satrni yasash — boshqa. JavaScript shablon satri qanday tur oladi?

Kod Hover'da
const x = `on${"ready"}` "onready"
let y = `on${"ready"}` string
const w = `on${e}` as const (e: string) `on${string}`

const bilan va qismlari aniq bo'lsa — aniq literal; let bilan — kengaygan string. as const esa naqsh turini saqlaydi.

Muammo — harf o'zgartirish. toUpperCase() natijasi — oddiy string, kompilyator Capitalize ni ishlash paytidagi kod bilan bog'lay olmaydi:

ts
function handlerName<E extends string>(
  event: E,
): `on${Capitalize<E>}` {
  return `on${event.charAt(0).toUpperCase()}${event.slice(1)}`;
}

Kompilyator:

text
TS2322: Type '`on${string}${string}`' is not assignable to type '`on${Capitalize<E>}`'.

"`on${string}${string}` turini `on${Capitalize<E>}` turiga berib bo'lmaydi". Kompilyator ko'rgani: "on + qandaydir satr + qandaydir satr". Birinchi harf haqiqatan katta bo'ldimi — u tekshira olmaydi.

Nega? TypeScript metodlarning ishini bilmaydi, faqat imzosini biladi. toUpperCase() imzosi — (): string: "satr qaytaraman". Qaysi satr — buni bilish uchun kodni bajarish kerak, kompilyator esa kodni bajarmaydi. Yana bir sabab: Capitalize<E> ni hisoblash uchun E aniq bo'lishi kerak, funksiya ichida esa u hali noma'lum.

Bu — Conditional types darsidagi holatning o'zi: natijani faqat as bilan berish mumkin. Bu as esa — tekshiruvsiz joy.

8. TypeScript ushlamaydigan tuzoq: tur va satr bir-biridan ajralib qolsa

8.1 as bilan yolg'on nom

Sardor handlerName ni as bilan yozdi — va Capitalize ni qilishni unutdi:

ts
type OrderEvent = "created" | "ready";
type Handlers = {
  [E in OrderEvent as `on${Capitalize<E>}`]: (t: number) => string;
};

const handlers: Handlers = {
  onCreated: (table) => `${table}-stol: yangi buyurtma`,
  onReady: (table) => `${table}-stol: tayyor`,
};

function handlerName<E extends OrderEvent>(event: E) {
  return `on${event}` as `on${Capitalize<E>}`;
}

const name = handlerName("ready");
console.log(handlers[name](5));

tsc — xatosiz. Node esa:

text
TypeError: handlers[name] is not a function

Turga ko'ra name — "onReady". Haqiqatda — "onready", kichik r bilan. handlers["onready"] — undefined, uni chaqirib bo'lmaydi. Template literal type — va'da, as esa uni tekshirmasdan qabul qildi.

Qoida: tur darajasidagi Capitalize, Uppercase ning ishlash paytidagi egizagi bo'lgan funksiyani bir marta yozing va test bilan tekshiring (mashqdagi kabi). Keyin hamma joyda shu funksiyani ishlating.

8.2 Naqsh — shakl, ma'no emas

ts
type Time = `${number}:${number}`;

function toMinutes(time: Time): number {
  const [hour = 0, minute = 0] = time.split(":").map(Number);
  return hour * 60 + minute;
}

function isBookable(time: Time): boolean {
  const m = toMinutes(time);
  return m >= 10 * 60 && m <= 22 * 60;
}

console.log(isBookable("19:30")); // true
console.log(isBookable("9:75")); // true

"9:75" — 9 soat 75 daqiqa. Bunday vaqt yo'q, lekin turdan o'tdi va hisob uni 10:15 deb oldi — "bron mumkin". Naqsh turi ${number}:${number} faqat shaklni tekshiradi: "son, ikki nuqta, son". 0–23 oralig'i, ikki xonali daqiqa — bular qiymat haqidagi qoidalar, ularni turlar emas, ishlash paytidagi kod tekshiradi. Mashqda shunday tekshiruvni yozasiz.

Xulosa — turlar va tekshiruvlar bir-birini to'ldiradi. Tur dasturchining xatosini ushlaydi: kodda "19.30" yozilsa, tsc darhol to'xtatadi. Ishlash paytidagi tekshiruv esa foydalanuvchining xatosini ushlaydi: mehmon formaga 9:75 yozsa, u turdan o'tgan bo'lsa ham rad etiladi. Ikkinchisini birinchisi bilan almashtirib bo'lmaydi — forma ma'lumoti tsc ishlaganda hali mavjud emas.

9. Ko'p uchraydigan xatolar

9.1 Ro'yxatda yo'q nom

TS2322 yoki TS2820 ("Did you mean …?") — hodisa nomida xato. Tuzatish: taklif qilingan variant yoki manba union'ga yangi a'zo.

9.2 Harf turiga satr bo'lmagan narsa

TS2344: Type 'number' does not satisfy the constraint 'string'. Tuzatish: Capitalize<K & string> yoki Capitalize<`${N}`> — avval matnga aylantiring.

9.3 as siz Capitalize natijasini qaytarish

TS2322: Type '`on${string}${string}`' is not assignable to type '`on${Capitalize<E>}`'. Tuzatish: as + test, yoki natija turini oddiyroq qiling.

9.4 Juftliklar portlashi

Uch-to'rt union'ni bitta shablonga qo'yish — yuzlab, minglab satr. Muharrir sekinlashadi, xabarlar o'qib bo'lmas holga keladi. Tuzatish: kombinatsiyalarni turga emas, ma'lumotga qo'ying ({ dish: Dish; size: Size } obyekti).

9.5 Naqshga ortiqcha ishonish

${number} — "0x1F" ni ham, "9:75" dagi 75 ni ham o'tkazadi. Tuzatish: ishlash paytida tekshiruv (RegExp yoki oraliq).

10. Mashqlar

1-mashq (oson): Kombinatsiyalarni sanang

ts
type Drink = "choy" | "kofe";
type Temp = "issiq" | "sovuq";
type DrinkKey = `${Drink}-${Temp}`;

const d: DrinkKey = "choy-sovuq";
const loud: Uppercase<"choy"> = "CHOY";
console.log(d, loud); // choy-sovuq CHOY

DrinkKey da nechta satr bor? [:4]. Capitalize<"kofe"> — "Kofe". Uppercase<"choy"> — [:CHOY].

Yechim

Ikki ichimlik × ikki harorat = 4: "choy-issiq", "choy-sovuq", "kofe-issiq", "kofe-sovuq". Uppercase hamma harfni katta qiladi — "CHOY"; Capitalize faqat birinchisini.

2-mashq (o'rta): CSS o'zgaruvchilari va getter'lar

  1. «Bahor» saytidagi mavzu ranglari: ThemeColor = "asosiy" | "fon" | "matn". CssVar — "--rang-asosiy" kabi CSS o'zgaruvchisi nomlari (CSS o'zgaruvchilari dagi -- prefiks).
  2. Getters<T> — har maydon uchun getName, getPrice kabi funksiya: parametrsiz, maydon turini qaytaradi.
Yechim
ts
type ThemeColor = "asosiy" | "fon" | "matn";
type CssVar = `--rang-${ThemeColor}`;

type Getters<T> = {
  [K in keyof T as `get${Capitalize<K & string>}`]: () => T[K];
};

type Dish = { name: string; price: number };
const osh = { name: "Osh", price: 35000 };
const getters: Getters<Dish> = {
  getName: () => osh.name,
  getPrice: () => osh.price,
};

const variable: CssVar = "--rang-fon";
console.log(variable, getters.getPrice()); // --rang-fon 35000

CssVar — uchta satr. Getters<Dish> — { getName: () => string; getPrice: () => number }. Bu — on${…} ishlovchilar bilan bir xil usul: kalitni qayta nomlash va qiymat turini funksiyaga o'rash.

3-mashq (qiyin): Amaliy tajriba — oshxona hodisalari

kurs/mashqlar/15/30-template/kitchen-events.ts faylini yarating:

  • OrderEvent — "created" | "ready" | "served"; EventName — `order:${OrderEvent}`; HandlerName<E> — `on${Capitalize<E>}`; Time — `${number}:${number}`.
  • eventOf(name: string) — "order:ready" → "ready", boshqa satr — null. Ishora: hodisalar ro'yxati as const bilan va for...of.
  • handlerName(event) — "ready" → "onReady"; natija turi HandlerName<E>.
  • toMinutes(time: Time) — "19:30" → 1170. Faqat aniq SS:DD shakli (ikki xonali) va haqiqiy vaqt (soat ≤ 23, daqiqa ≤ 59); aks holda RangeError. Ishora: RegExp — /^(\d{2}):(\d{2})$/ va exec.

Avval handlerName ni as siz yozib, npx tsc --pretty false ni ishga tushiring. Testlar: eventOf (to'g'ri va begona nom), handlerName (ikki hodisa), toMinutes (to'g'ri vaqtlar; "9:75", "1e1:00", "24:00" — RangeError).

Yechim

as siz variantda — «Qiymatlar dunyosida» bo'limidagi xato, endi faylda:

text
30-template/kitchen-events.ts(20,3): error TS2322: Type '`on${string}${string}`' is not assignable to type '`on${Capitalize<E>}`'.
ts
// 30-template/kitchen-events.ts — «Bahor» oshxona hodisalari
export type OrderEvent = "created" | "ready" | "served";
export type EventName = `order:${OrderEvent}`;
export type HandlerName<E extends string> = `on${Capitalize<E>}`;
export type Time = `${number}:${number}`;

// "order:ready" → "ready"; begona nom — null
export function eventOf(name: string): OrderEvent | null {
  for (const event of ["created", "ready", "served"] as const) {
    if (name === `order:${event}`) return event;
  }
  return null;
}

// "ready" → "onReady" (tur ham, qiymat ham)
export function handlerName<E extends string>(
  event: E,
): HandlerName<E> {
  const first = event.charAt(0).toUpperCase();
  return `on${first}${event.slice(1)}` as HandlerName<E>;
}

// "19:30" → 1170; tur faqat shaklni tekshiradi, qolgani — bizda
const TIME_RE = /^(\d{2}):(\d{2})$/;

export function toMinutes(time: Time): number {
  const match = TIME_RE.exec(time);
  if (match === null) {
    throw new RangeError(`Vaqt "SS:DD" bo'lsin: ${time}`);
  }
  const hour = Number(match[1]);
  const minute = Number(match[2]);
  if (hour > 23 || minute > 59) {
    throw new RangeError(`Bunday vaqt yo'q: ${time}`);
  }
  return hour * 60 + minute;
}
  • eventOf — as const ro'yxat elementi event turi "created" | "ready" | "served", shuning uchun return event OrderEvent ga mos.
  • handlerName dagi as — darsdagi tuzoq joyi. Uni test himoya qiladi: harfni katta qilish unutilsa, test yiqiladi.
  • "1e1:00" — qiziq holat: ${number} uni o'tkazadi, Number("1e1") esa 10. RegExp bo'lmasa, bu 10:00 bo'lib o'tib ketardi.
ts
// 30-template/kitchen-events.test.ts
import { test } from "node:test";
import assert from "node:assert/strict";
import { eventOf, handlerName, toMinutes } from "./kitchen-events.ts";

test("eventOf: nomdan hodisa", () => {
  assert.equal(eventOf("order:ready"), "ready");
  assert.equal(eventOf("table:free"), null);
});

test("handlerName: birinchi harf katta", () => {
  assert.equal(handlerName("ready"), "onReady");
  assert.equal(handlerName("served"), "onServed");
});

test("toMinutes: to'g'ri vaqt", () => {
  assert.equal(toMinutes("19:30"), 1170);
  assert.equal(toMinutes("07:00"), 420);
});

test("toMinutes: tur o'tkazgan, lekin yo'q vaqt", () => {
  assert.throws(() => toMinutes("9:75"), RangeError);
  assert.throws(() => toMinutes("1e1:00"), RangeError);
  assert.throws(() => toMinutes("24:00"), RangeError);
});

npx tsc --pretty false — chiqishsiz. node --test 30-template/kitchen-events.test.ts bizda (Node 24.21.0):

text
✔ eventOf: nomdan hodisa (1.7145ms)
✔ handlerName: birinchi harf katta (0.3313ms)
✔ toMinutes: to'g'ri vaqt (0.4512ms)
✔ toMinutes: tur o'tkazgan, lekin yo'q vaqt (0.9476ms)
ℹ tests 4
ℹ suites 0
ℹ pass 4
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 259.8732

XATOLAR.md ga qo'shing: TS2820 ("Did you mean"), TS2344 (Capitalize<number>), TS2322 ning `on${string}${string}` ko'rinishi. Keyin kurs/mashqlar papkasidan:

bash
git add 15/30-template 15/XATOLAR.md
git commit -m "15/30: kitchen-events.ts — template literal types"

11. Real ishda

  • Hodisa nomlari va ishlovchilar: DOM'dagi addEventListener turlari ("click" → PointerEvent) — xuddi KitchenEvents kabi jadval. React'da onClick, onChange props nomlari ham shu naqshda (17-qismdan keyin).
  • Yo'l (route) parametrlari: Express, Next.js kabi freymvorklar (24-qism va keyin) "/menu/:id" dan { id: string } turini aynan infer + template literal bilan chiqaradi.
  • Tarjima kalitlari: ko'p tilli saytlarda matnlar "menu.title", "order.ready" kabi kalitlar bilan olinadi. Kalitlar turi tarjima faylidan template literal bilan yasaladi — yo'q kalitni yozsangiz, tsc ushlaydi.
  • CSS va dizayn tizimlari: --rang-${…}, `${number}px` kabi naqshlar — dizayn tokenlari turlarida.
  • Kutubxona muallifi darajasi: murakkab satr tahlili (SQL so'rovini tur darajasida o'qish kabi) — kutubxonalarda uchraydi, lekin kompilyatorni sekinlashtiradi. Ilova kodida oddiy prefiks va Capitalize yetadi.
  • Intervyu: "Template literal type nima?", "Capitalize qanday ishlaydi?", "onX ishlovchilar turini yasang" — o'rta darajadagi savollar.

Xulosa

  • `order:${OrderEvent}` — satr turlarini shablondan yasash; union qo'yilsa — hamma kombinatsiyalar (soni ko'paytma bilan o'sadi).
  • Uppercase, Lowercase, Capitalize, Uncapitalize — kompilyatorga qurilgan (intrinsic) harf turlari.
  • Mapped type + as + shablon → onCreated, getPrice kabi kalitlar.
  • ${number}, ${string} — naqshlar: shaklni tekshiradi, ma'noni emas ("0x1F", "9:75" o'tadi).
  • infer shablon ichida satrni bo'laklaydi. Ishlash paytidagi satrni esa siz yasaysiz — as bilan bergan va'dani test bilan tekshiring.

Keyingi dars: Branded types va variance — bir xil shakldagi, lekin ma'nosi farqli qiymatlarni (TableId va OrderId) adashtirmaslik.

Manbalar

  • TypeScript Handbook: "Template Literal Types" (Intrinsic String Manipulation Types) — typescriptlang.org/docs/handbook/2/template-literal-types.html
  • TypeScript Release Notes 4.1 (template literal types, key remapping) va 4.5 (template string types as discriminants) — typescriptlang.org/docs/handbook/release-notes/
  • lib.es5.d.ts — Uppercase, Lowercase, Capitalize, Uncapitalize (intrinsic)
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
TypeScript template literal types: satr turlarini shablon bilan yasash — IlmHamroh