16 β Template literal types¶
β¬ οΈ Oldingi: 15 β Mapped va conditional types Β· π README Β· Keyingi: 17 β Modullar va deklaratsiya fayllari (.d.ts) β‘οΈ
Bu bobda: ko'pincha bizga oddiy
stringemas, balki aniq shakldagi string kerak bo'ladi β masalan"GET /api/users","onClick","16px"yoki"top-left". Bunday string'lar ixtiyoriy matn emas, ular qat'iy qolipga bo'ysunadi. Template literal type (string shabloni tipi) β JavaScript'dagi backtick string'larining tip darajasidagi ko'rinishi: qattiq matn qismlari va${tip}joylarini birlashtirib, aniq string shaklini ifodalaymiz. Bu bobda template literal type'ning anatomiyasini, union'lar bilan birlashganda qanday "ko'paytma" hosil bo'lishini, ichki (intrinsic)Uppercase/Lowercase/Capitalize/Uncapitalizeutility'larini, mapped type bilan birga event nomlari va getter'lar yaratishni, hamdainferorqali route param'larni ajratib olishni real, tekshirilgan kod bilan ko'rib chiqamiz.
Muammo¶
Tasavvur qiling, jamoada API endpoint'larini matn sifatida uzatadigan funksiya bor:
function sorov(endpoint: string): void {
console.log("Yuborilmoqda:", endpoint);
}
sorov("GET /api/users"); // to'g'ri
sorov("get /api/uzers"); // β kichik harf, ikki bo'sh joy, "uzers" β lekin TS indamaydi
endpoint: string deganimizda TypeScript faqat "bu matn" deyishni biladi. "GET /api/users" ham, "qwertyuiop" ham, bo'sh string "" ham bir xil β hammasi string. Aslida bizga kerak bo'lgan narsa: "bu string aynan METOD /api/RESURS shaklida bo'lsin". Ya'ni:
- boshida faqat
GET,POSTyokiDELETE; - keyin
/api/; - oxirida faqat
usersyokiposts.
7-bobda literal tipni o'rgangandik: "GET" β bu tip, faqat "GET" qiymatini qabul qiladi. Lekin har bir to'liq endpoint'ni qo'lda yozish ("GET /api/users" | "GET /api/posts" | "POST /api/users" | ...) zerikarli va xatoga moyil. Yangi resurs qo'shsangiz, hamma kombinatsiyani qaytadan yozasiz.
Mana shu yerda template literal type yordamga keladi: biz "qolip"ni bir marta yozamiz, TypeScript esa hamma mumkin bo'lgan string'larni o'zi hisoblab chiqadi.
type Metod = "GET" | "POST" | "DELETE";
type Resurs = "users" | "posts";
type Endpoint = `${Metod} /api/${Resurs}`;
// Endpoint = "GET /api/users" | "GET /api/posts" | "POST /api/users" | ...
Template literal type β asoslar¶
Template literal type JavaScript'dagi template string'ning aynan o'zi, faqat qiymat darajasida emas, tip darajasida ishlaydi. Sintaksis bir xil: backtick () ichida qattiq matn va${...}joylar. Farqi shundaki,${...}` ichiga qiymat emas, tip yoziladi.
type Salom = `Salom, ${string}!`;
const a: Salom = "Salom, Aziz!"; // β
qolipga mos
const b: Salom = "Salom, dunyo!"; // β
qolipga mos
O'qilishi: "Salom, matni + ixtiyoriy string + ! matni shaklidagi har qanday string". Qolipga mos kelmasa, kompilyator darrov ushlaydi:
π ${string} β bu "shu joyda ixtiyoriy string bo'lsin" degani. Demak Salom tipi cheksiz ko'p qiymatni qamraydi ("Salom, !", "Salom, 12345!" ham). Cheklash uchun string o'rniga union literal qo'yiladi β buni keyingi bo'limda ko'ramiz.
π‘ Backtick () belgisi β bu ASCII tirnoq emas. Klaviaturada odatdaEsctugmasi ostida,1` chap tomonida turadi. JavaScript template string'da nimani ishlatgan bo'lsangiz, tip yozishda ham aynan o'shani ishlatasiz.
${...} ichiga qaysi tiplarni qo'yish mumkin¶
Template ichidagi joyga string'dan tashqari number, boolean, bigint va, eng muhimi, literal union ham qo'yiladi. TypeScript ularni avtomatik string'ga "aylantirib" qolipga qo'shadi:
type Piksel = `${number}px`;
const p1: Piksel = "16px"; // β
const p2: Piksel = "0px"; // β
type Bayroq = `flag-${boolean}`;
const f1: Bayroq = "flag-true"; // β
const f2: Bayroq = "flag-false"; // β
π ${number} β "shu joyda raqamga o'xshash string" degani: "16px", "0px", "-5px", hatto "3.14px" ham mos keladi. Lekin "abcpx" mos kelmaydi β abc raqam emas. Bu CSS o'lchamlari, ID'lar, versiya raqamlari uchun juda qulay.
Union bilan birlashganda β "ko'paytma" hosil bo'ladi¶
Template literal type'ning eng kuchli tomoni shu: ${...} ichiga union literal qo'ysangiz, TypeScript har bir variant uchun alohida string yaratadi. Ikkita union qo'ysangiz β ularning har bir juftligi uchun.
type Vertikal = "top" | "bottom";
type Gorizontal = "left" | "right";
type Burchak = `${Vertikal}-${Gorizontal}`;
// Burchak = "top-left" | "top-right" | "bottom-left" | "bottom-right"
const b1: Burchak = "top-left"; // β
const b2: Burchak = "bottom-right"; // β
Vertikalda 2 ta variant, Gorizontalda 2 ta β natijada 2 x 2 = 4 ta aniq string. Bu xuddi matematik ko'paytma kabi ishlaydi: har bir Vertikal har bir Gorizontal bilan juftlanadi.
Qolipga mos kelmaydigan kombinatsiya darrov ushlanadi β va kompilyator xabarida hosil bo'lgan to'liq unionni ko'rasiz:
error TS2322: Type '"top-center"' is not assignable to type
'"top-left" | "top-right" | "bottom-left" | "bottom-right"'.
π‘ Diqqat: ko'paytma tez o'sadi. 3 ta union'ni birlashtirsangiz (a x b x c), variantlar soni ko'payadi: 3 x 3 x 3 = 27. Juda katta union'lar bilan ehtiyot bo'ling β TypeScript bir tipda taxminan 100 000 dan ortiq variantni hisoblamaydi va xato beradi. Real loyihada bu chegaraga kamdan-kam yetasiz, lekin "har ehtimolga qarshi hamma narsani template'ga tiqaman" degan vasvasaga berilmang.
Ichki string utility tiplari¶
TypeScript template literal type uchun 4 ta tayyor (intrinsic β tilning ichiga qurib qo'yilgan) string o'zgartiruvchi tipni beradi. Ularni alohida import qilish shart emas, hamma joyda mavjud:
| Utility | Vazifasi | Misol |
|---|---|---|
Uppercase<S> |
hamma harfni KATTA | Uppercase<"salom"> -> "SALOM" |
Lowercase<S> |
hamma harfni kichik | Lowercase<"SALOM"> -> "salom" |
Capitalize<S> |
faqat birinchi harfni katta | Capitalize<"salom"> -> "Salom" |
Uncapitalize<S> |
faqat birinchi harfni kichik | Uncapitalize<"Salom"> -> "salom" |
type A = Uppercase<"salom">; // "SALOM"
type B = Lowercase<"SALOM">; // "salom"
type C = Capitalize<"salom">; // "Salom"
type D = Uncapitalize<"Salom">; // "salom"
const a: A = "SALOM"; // β
const c: C = "Salom"; // β
Bular union bilan ham ishlaydi β har bir variantga alohida qo'llanadi:
π Bu utility'lar tip darajasida ishlaydi β runtime'da hech narsa bajarmaydi. Ya'ni Uppercase<...> kodga .toUpperCase() chaqiruvini qo'shmaydi. U faqat kompilyatorga string'ning shaklini tasvirlaydi. Runtime'da haqiqatan harf o'zgartirish kerak bo'lsa, JavaScript'dagi .toUpperCase() ni o'zingiz chaqirasiz.
Capitalize + template β event nomlari¶
Eng ko'p uchraydigan amaliy qolip: maydon nomidan event handler nomini yasash. React'da onClick, onChange, onSubmit kabi nomlar shunday tug'iladi:
type Maydon = "name" | "email" | "age";
type EventNomi = `on${Capitalize<Maydon>}`;
// EventNomi = "onName" | "onEmail" | "onAge"
const e1: EventNomi = "onName"; // β
const e2: EventNomi = "onEmail"; // β
Capitalize bo'lmaganida "onname" hosil bo'lardi β ko'zga xunuk. Capitalize<Maydon> har bir variantning bosh harfini kattalashtiradi, keyin on prefiksi old tomonga ulanadi.
error TS2820: Type '"onname"' is not assignable to type
'"onName" | "onEmail" | "onAge"'. Did you mean '"onName"'?
π‘ Kompilyator xabaridagi Did you mean '"onName"'? qismi β TypeScript sizga eng yaqin to'g'ri variantni taklif qilayotgani. Template literal type'lar tufayli muharrir (editor) ham aniq nomlarni avtomat to'ldiradi.
Mapped type + template β obyekt kalitlarini qayta yozish¶
15-bobda mapped type (obyekt kalitlari bo'ylab aylanish) va as orqali kalitni qayta nomlashni ko'rgandik. Template literal type aynan shu as ichida ishlatilganda haqiqiy kuchini ko'rsatadi: bitta manba tipdan butun bir handler yoki getter to'plamini avtomatik yasaymiz.
type Holatlar = { name: string; age: number };
type Handlerlar = {
[K in keyof Holatlar as `on${Capitalize<string & K>}Change`]: (yangi: Holatlar[K]) => void;
};
// Handlerlar = {
// onNameChange: (yangi: string) => void;
// onAgeChange: (yangi: number) => void;
// }
const h: Handlerlar = {
onNameChange: (yangi) => console.log(yangi.toUpperCase()), // yangi: string
onAgeChange: (yangi) => console.log(yangi + 1), // yangi: number
};
E'tibor bering: onNameChange ichida yangi avtomatik string, onAgeChange ichida esa number deb bilinadi β chunki tip har bir kalitning asl tipini (Holatlar[K]) saqlab qoldi.
π string & K nimaga kerak? keyof natijasi string | number | symbol bo'lishi mumkin, Capitalize esa faqat string bilan ishlaydi. string & K (intersection β 10-bobda ko'rgan) Kni "string qismi"ga toraytiradi, shunda Capitalize shikoyat qilmaydi. Bu mapped + template qolipida deyarli har doim yoziladigan standart hiyla.
Getter va setter generatori¶
Bir manba tipdan getter va setter'larni birato'la yasash mumkin (mapped type'larni & bilan birlashtirib):
type Manba = { ism: string; yosh: number };
type Accessorlar =
& { [K in keyof Manba as `get${Capitalize<string & K>}`]: () => Manba[K] }
& { [K in keyof Manba as `set${Capitalize<string & K>}`]: (v: Manba[K]) => void };
const acc: Accessorlar = {
getIsm: () => "Aziz",
getYosh: () => 30,
setIsm: (v) => console.log(v), // v: string
setYosh: (v) => console.log(v), // v: number
};
Bitta Manbaga yangi maydon qo'shsangiz, getter va setter o'zi paydo bo'ladi β qo'lda bironta qator yozish shart emas. Mana shu template literal type'ning amaliy qiymati: takrorni nolga tushiradi.
CSS xossalari β yana bir real misol¶
Template literal type CSS bilan ishlashda ham qo'l keladi. Masalan, faqat to'rt tomonning margin xossasini ifodalash:
type Yon = "top" | "bottom" | "left" | "right";
type MarginXossa = `margin-${Yon}`;
// "margin-top" | "margin-bottom" | "margin-left" | "margin-right"
const x: MarginXossa = "margin-top"; // β
error TS2322: Type '"padding-top"' is not assignable to type
'"margin-bottom" | "margin-left" | "margin-right" | "margin-top"'.
π‘ Brauzerda ishlashda (19-bob) bunday tiplar element.style.setProperty(...) ga uzatiladigan xossa nomlarini xato yozishdan saqlaydi.
Route param'larni ajratib olish (infer bilan)¶
Eng ilg'or qolip: template literal type'ni 15-bobdagi conditional type va infer (tip ichidan bo'lakni "tutib olish") bilan birlashtirsak, string'ni qismlarga ajrata olamiz. Klassik misol β "/users/:id" kabi route yo'lidan param nomlarini chiqarib, ulardan obyekt tipi yasash:
type Params<S extends string> =
S extends `${string}:${infer P}/${infer Qolgan}`
? { [K in P | keyof Params<`/${Qolgan}`>]: string }
: S extends `${string}:${infer P}`
? { [K in P]: string }
: {};
type R1 = Params<"/users/:id">;
// { id: string }
type R2 = Params<"/users/:id/posts/:postId">;
// { id: string; postId: string }
const p1: R1 = { id: "42" }; // β
const p2: R2 = { id: "42", postId: "7" }; // β
Bu qanday ishlaydi? Yo'lni qadama-qadam o'qiymiz:
S extends${string}:${infer P}/${infer Qolgan}` β string ichida:bormi va undan keyin/bilan davom etadimi? Bo'lsa,:dan keyingi qismniPga (masalanid),/dan keyingisiniQolgan` ga olamiz.Pni kalit qilamiz vaQolganqismni rekursiv qayta tahlil qilamiz (Params</${Qolgan}>) β shunday qilib bir nechta param yig'iladi.- Agar
/qolmasa, lekin:bo'lsa β bu oxirgi param, uni kalit qilamiz. - Hech qaysi
:topilmasa β param yo'q, bo'sh obyekt{}.
Param yetishmasa, kompilyator buni ham ushlaydi:
π Bu qolip kuchli, lekin murakkab β har kuni yozadigan kod emas. Asosan kutubxona mualliflari (router, query builder) shunday tiplar yozadi, oddiy ilova kodida kamdan-kam kerak bo'ladi. Lekin kutubxona "sehrini" tushunish uchun bilib qo'ygan foydali.
API endpoint tiplari β hammasini birlashtirib¶
Boshidagi muammoga qaytamiz. Endi endpoint string'larini qat'iy tiplay olamiz:
type Metod = "GET" | "POST" | "DELETE";
type Resurs = "users" | "posts";
type Endpoint = `${Metod} /api/${Resurs}`;
function sorov(endpoint: Endpoint): void {
console.log("Yuborilmoqda:", endpoint);
}
sorov("GET /api/users"); // β
sorov("POST /api/posts"); // β
error TS2345: Argument of type '"get /api/users"' is not assignable to parameter of type
'"GET /api/users" | "GET /api/posts" | "POST /api/users" | "POST /api/posts" | "DELETE /api/users" | "DELETE /api/posts"'.
Endi string qabul qilganda yo'l qo'yilgan hamma xatolar β kichik harf, noto'g'ri resurs nomi, ortiqcha bo'sh joy β kompilyatsiya bosqichidayoq, kod ishga tushmasdan ushlanadi.
satisfies bilan jadval tekshirish¶
13-bobda satisfies operatorini ko'rgandik. Uni template literal type bilan birga ishlatib, konfiguratsiya obyektidagi har bir qiymat to'g'ri shaklda ekanini tekshirish mumkin β qiymatlarning aniq tipini yo'qotmasdan:
const yollar = {
bosh: "/",
profil: "/profil",
sozlamalar: "/sozlamalar",
} satisfies Record<string, `/${string}`>;
// har bir qiymat "/" bilan boshlanishi MAJBURIY,
// lekin yollar.bosh tipi hali ham aniq "/" bo'lib qoladi
π‘ Agar biror qiymatni "profil" (boshida / yo'q) deb yozsangiz, satisfies shu yerdayoq xato beradi. Bu konfiguratsiya fayllarini "tip-xavfsiz" qilishning toza usuli.
Qachon ishlatish, qachon ishlatmaslik¶
Template literal type β o'tkir asbob. To'g'ri joyda ajoyib, noto'g'ri joyda kodni o'qib bo'lmas qiladi.
β
Ishlating: cheklangan, oldindan ma'lum string shakllari uchun β CSS xossalari, event nomlari, API endpoint'lar, ID prefikslari (user_123), i18n kalitlar.
β Ishlating: mapped type bilan birga, manba tipdan handler/getter to'plamini avtomatik yasashda β takrorni yo'qotadi.
β Ishlatmang: string ichidagi cheksiz xilma-xil matn uchun (foydalanuvchi kiritgan izoh, erkin matn) β bu yerda oddiy string to'g'riroq.
β Ishlatmang: ko'paytma juda katta bo'lib ketadigan joyda (4-5 ta katta union'ni birlashtirish). Murakkablik foydadan oshib ketadi.
π Oltin qoida: agar tip yozish kodni o'qishni osonlashtirsa va xatolarni oldindan ushlasa β ishlating. Agar tip o'zi jumboqqa aylanib, jamoadagi hech kim tushunmasa β soddaroq yo'l (oddiy union yoki hatto string) tanlang. Template literal type "ko'rsatish uchun" emas, muammoni hal qilish uchun.
16-bob mashqlari¶
Quyidagi mashqlarni alohida .ts faylda yozib, tsc --noEmit --strict bilan tekshiring. Qiyinlashib boradi β har bir mashq oldingisining ustiga quriladi.
-
Salomlashuvnomli template literal type yarating:`Assalomu alaykum, ${string}`shaklida. Unga mos va mos kelmaydigan ikkita qiymat yozib, kompilyator qaysi birini rad etishini ko'ring. -
FoydalanuvchiID'si uchun`user_${number}`tipini yarating."user_1","user_999"o'tishini,"user_abc"o'tmasligini tasdiqlang. -
Hajm = "sm" | "md" | "lg"union'idan`btn-${Hajm}`tipini yasang. Hosil bo'lgan uch variantni izohda yozing. -
Faylga prefiks va suffiks qo'shing:
`img-${string}.png`tipini yarating."img-logo.png"o'tishini,"logo.png"(prefiks yo'q) va"img-logo.jpg"(suffiks boshqa) o'tmasligini tekshiring. -
Til = "uz" | "en"vaSahifa = "home" | "about"union'laridan`${Til}/${Sahifa}`tipini yasang. Necha variant hosil bo'lishini avval o'zingiz hisoblang, keyin kompilyator xabaridan tekshiring. -
Uppercasebilan:Yonalish = "shimol" | "janub"union'idanUppercase<Yonalish>tipini yarating va natijani izohda yozing. -
Capitalizebilan:"login" | "logout"union'idan`on${Capitalize<...>}`orqali"onLogin" | "onLogout"tipini yasang. -
LowercasevaUncapitalizefarqini ko'rsating:"HELLO"ga ikkalasini ham qo'llab, natijalardagi farqni izohlang. -
CSS uchun
`padding-${"top" | "right" | "bottom" | "left"}`tipini yarating va to'rtta to'g'ri qiymatni sinab ko'ring. -
Holat = "active" | "disabled"union'idan`is-${Holat}`va`not-${Holat}`tiplarini yasab, ularni|bilan bittaKlasstipiga birlashtiring. -
Resurs = "user" | "post" | "comment"vaAmal = "create" | "delete"union'laridan`${Amal}-${Resurs}`permission (ruxsat) tipini yarating. Necha ta ruxsat hosil bo'ldi? -
Mapped type bilan:
{ id: number; title: string }obyektidan har bir kalit uchun`get${Capitalize<...>}`getter'larni avtomatik yasovchi tip yozing (string & Khiylasini eslang). -
12-mashqni kengaytiring: getter'lar yonida
`set${Capitalize<...>}`setter'larni ham qo'shing (ikki mapped type'ni&bilan birlashtiring). -
{ name: string; age: number; email: string }dan`on${Capitalize<...>}Change`event handler'lar tipini yasang. Har bir handler argumenti asl maydon tipini saqlab qolishini tekshiring. -
Metod = "GET" | "POST"vaResurs = "users" | "orders"dan`${Metod} /api/${Resurs}`Endpoint tipini yarating. Funksiya yozib, faqat to'g'ri endpoint'lar qabul qilinishini ko'rsating. -
satisfiesbilan:Record<string,#${string}>ga mosranglarobyektini yarating (har bir qiymat#bilan boshlansin, masalan"#ff0000"). Boshida#yo'q qiymat yozib, xato chiqishini ko'ring. -
Conditional type va
inferbilan:`${infer Prefiks}_${string}`orqali"user_42"dan"user"prefiksini ajratib oluvchiPrefiksni<S>tipini yozing. -
Route param:
Params<"/posts/:slug">tipi{ slug: string }berishini ta'minlovchi conditional type yozing (bitta param uchun, rekursiyasiz). -
18-mashqni rekursiv qiling:
Params<"/users/:id/posts/:postId">ikkala param'ni ham{ id: string; postId: string }ko'rinishida bersin. Bobdagi qolipdan foydalaning va to'g'ri ishlashini ikkita misol bilan tekshiring. -
Hammasini birlashtiring: i18n uchun
`${Til}.${Modul}.${Kalit}`shaklidagi tarjima kaliti tipini yarating (Til = "uz" | "en",Modul = "auth" | "profile",Kalit = "title" | "submit"). Necha variant hosil bo'lishini hisoblang, keyinCapitalizeqo'shib har bir kalitdan event nomi (`on${Capitalize<Kalit>}`) yasab, ko'paytma qanchalik tez o'sishini his qiling β va qachon bu yondashuv "ortiqcha" bo'lishini o'z so'zlaringiz bilan izohlang.