Mundarija (31)
- Bu darsda
- 1. Nega bu kerak?
- 2. Branded type: turga tamg'a bosish
- 2.1 G'oya
- 2.2 Kompilyator qanday solishtiradi
- 2.3 Tamg'a faqat turda — ishlash paytida yo'q
- 3. Yagona eshik: tekshiruvchi konstruktor
- 3.1 Qachon tamg'a kerak
- 3.2 Kutubxonalardagi yozuv
- 4. Variance: tur munosabatlari qaysi yo'nalishda o'tadi
- 4.1 Boshlang'ich munosabat
- 4.2 Kovariantlik: "o'qiladigan" joylar
- 4.3 Kontravariantlik: parametrlar
- 4.4 strictFunctionTypes
- 5. TypeScript ataylab ko'z yumadigan ikki joy
- 5.1 O'zgaruvchan massiv — kovariant (aslida xavfli)
- 5.2 Metod sintaksisi — ikki tomonlama
- 6. in va out — kutubxona muallifi darajasi
- 7. Ko'p uchraydigan xatolar
- 7.1 Taxallusni yangi tur deb o'ylash
- 7.2 Oddiy sonni tamg'ali joyga berish
- 7.3 as ni hamma joyda yozish
- 7.4 Tamg'ani ishlash paytida izlash
- 7.5 Callback'ni metod sintaksisida yozish
- 8. Mashqlar
- 1-mashq (oson): Qaysi biri o'tadi?
- 2-mashq (o'rta): Variance'ni bashorat qiling
- 3-mashq (qiyin): Amaliy tajriba — stol va buyurtma raqamlari
- 9. Real ishda
- Xulosa
- Manbalar
TypeScript branded types va variance: OrderId ni TableId dan ajratish
Qisqacha: TypeScript turlarni shakli bo'yicha solishtiradi, shuning uchun ikkala
number— stol raqami ham, buyurtma raqami ham — bir xil tur. Branded type songa faqat turda yashaydigan "tamg'a" qo'shadi:number & { readonly __brand: "OrderId" }— endiTableIdniOrderIdo'rniga berib bo'lmaydi. Variance esa "Drink—MenuItemning bir turi bo'lsa,Drink[]va(d: Drink) => voidqaysi yo'nalishda mos keladi?" degan savolga javob beradi.
Bu darsda
- Strukturaviy tiplash nega
OrderIdvaTableIdni farqlay olmasligini tushuntira olasiz. - Branded type va uni yasaydigan tekshiruvchi funksiya ("yagona eshik") yozasiz.
- Kovariantlik va kontravariantlikni misol bilan ko'rsatib,
strictFunctionTypesnima qilishini aytasiz. - TypeScript ataylab "ko'z yumadigan" ikki joyni — o'zgaruvchan massiv va metod sintaksisini — taniysiz.
in/outmodifikatorlarini kutubxona kodida o'qiy olasiz.
Oldin bilishingiz kerak: Strukturaviy tiplash, Kengaytirish, kesishma va declaration merging, Template literal types.
1. Nega bu kerak?
O'tgan darslarda turlarni bir-biridan hisoblab chiqardik. Bugun teskari muammo: ikki tur bir xil ko'rinadi, lekin ularni aralashtirib bo'lmaydi. Bu dars "C" qavatidan — chuqur. Uning ikkinchi yarmi (variance) asosan kutubxona yozadiganlar uchun, lekin xatolarini o'qish har bir dasturchiga kerak.
«Bahor» zalida 20 ta stol bor, buyurtmalar raqami esa 101 dan boshlanadi. Ikkalasi ham son. Sardor buyurtmani yopadigan funksiya yozdi:
type Order = { id: number; table: number; closed: boolean };
const orders: Order[] = [
{ id: 101, table: 5, closed: false },
{ id: 102, table: 3, closed: false },
];
function closeOrder(orderId: number): void {
const order = orders.find((o) => o.id === orderId);
if (order) order.closed = true;
}
const table = 3;
closeOrder(table);
console.log(orders.map((o) => o.closed)); // [ false, false ]Ofitsiant 3-stoldagi buyurtmani yopmoqchi edi. Sardor esa funksiyaga stol raqamini berdi. 3-raqamli buyurtma yo'q — hech narsa yopilmadi, xato ham chiqmadi. Buyurtmalar 3 dan boshlanganda esa butunlay boshqa stolning buyurtmasi yopilardi.
TypeScript buni ushlamaydi. Strukturaviy tiplash qoidasi: tur nomi emas, shakli muhim. orderId: number va table: number — bir xil shakl. Hatto ularga turli nom bersak ham:
type OrderId = number;
type TableId = number;
function closeOrder(id: OrderId): void {
console.log(`${id}-buyurtma yopildi`);
}
const table: TableId = 3;
closeOrder(table); // 3-buyurtma yopilditype OrderId = number — yangi tur emas, number ning taxallusi. Taxallus hujjat sifatida foydali, lekin kompilyator uchun OrderId va TableId — ikkalasi ham number.
2. Branded type: turga tamg'a bosish
2.1 G'oya
Branded type (tamg'alangan tur) — oddiy turga faqat tur darajasida mavjud bo'lgan farqlovchi belgi qo'shilgan tur. Kunlik o'xshatish: bozordagi go'sht. Ikki bo'lak go'sht bir xil ko'rinadi, lekin birida veterinariya tamg'asi bor. Sotuvchi tamg'asizini "tamg'ali" deb sota olmaydi.
TypeScript'da tamg'a — kesishma orqali qo'shilgan xayoliy xususiyat:
type OrderId = number & { readonly __brand: "OrderId" };
type TableId = number & { readonly __brand: "TableId" };
function closeOrder(id: OrderId): void {}
const table = 5 as TableId;
closeOrder(table);
closeOrder(7);Kompilyator:
TS2345: Argument of type 'TableId' is not assignable to parameter of type 'OrderId'.
Type 'TableId' is not assignable to type '{ readonly __brand: "OrderId"; }'.
Types of property '__brand' are incompatible.
Type '"TableId"' is not assignable to type '"OrderId"'.
TS2345: Argument of type 'number' is not assignable to parameter of type 'OrderId'.
Type 'number' is not assignable to type '{ readonly __brand: "OrderId"; }'.Endi ikki xato ham ushlandi:
- Birinchisi:
TableIdniOrderIdga berib bo'lmaydi. Zanjirni pastdan o'qing: "__brandxususiyatlari mos emas:"TableId"≠"OrderId"". - Ikkinchisi: oddiy
7ham o'tmaydi — unda tamg'a umuman yo'q.
2.2 Kompilyator qanday solishtiradi
TableId ni OrderId ga berishda tsc shunday tekshiradi:
| Qadam | Tekshiruv | Natija |
|---|---|---|
| 1 | OrderId — kesishma: ikkala qismga ham mos bo'lish kerak |
number va { __brand: "OrderId" } |
| 2 | TableId → number qismi |
mos: ikkalasi ham son |
| 3 | TableId → { __brand: "OrderId" } qismi |
__brand bor, lekin qiymati "TableId" |
| 4 | "TableId" → "OrderId" |
literal turlar farq qiladi — rad |
Strukturaviy tiplash o'zgarmadi — biz shunchaki shakllarni farqli qildik. Endi TableId va OrderId ning shakli bitta xususiyat bilan farq qiladi.
2.3 Tamg'a faqat turda — ishlash paytida yo'q
Eng muhim nuqta. __brand xususiyati hech qachon mavjud emas:
type OrderId = number & { readonly __brand: "OrderId" };
const id = 101 as OrderId;
console.log(typeof id); // number
console.log(id + 1); // 102
console.log(JSON.stringify({ id })); // {"id":101}id — oddiy son. Node uni 101 deb biladi, JSON ham. Tur esa "__brand bor" deb yolg'on gapiradi — lekin bu foydali yolg'on: uni hech kim o'qimaydi, u faqat kompilyatorga farqlash uchun kerak. id + 1 natijasi — oddiy number: arifmetikadan keyin tamg'a tushib qoladi. Bu to'g'ri — 101 + 1 yangi buyurtma raqami ekanini hech kim kafolatlamaydi.
Tamg'ani qo'yishning yagona yo'li — as (Type assertion). Bu xavfli: as OrderId ni istalgan songa yozish mumkin. Shuning uchun keyingi qadam.
Tekshirib ko'ring:
type Sum = number & { readonly __brand: "Sum" }bo'lsa,const total: number = someSum;ruxsat etiladimi? Teskarisi-chi?
Javob
Sum ni number ga berish mumkin: Sum — kesishma, unda number qismi bor, ya'ni u "son va yana nimadir". number ni Sum ga berib bo'lmaydi — unda tamg'a yo'q. Tamg'ali qiymat oddiy kutilgan joyga bemalol boradi, oddiy qiymat tamg'ali joyga — faqat tekshiruvchi funksiya orqali.
3. Yagona eshik: tekshiruvchi konstruktor
as ni butun kod bo'ylab sochmaslik uchun tamg'ani bitta funksiya qo'yadi — va qo'yishdan oldin qiymatni tekshiradi. Bunday funksiyani ba'zan "aqlli konstruktor" (smart constructor) deyishadi:
type TableId = number & { readonly __brand: "TableId" };
function tableId(value: number): TableId {
if (!Number.isInteger(value) || value < 1 || value > 20) {
throw new RangeError(`Bunday stol yo'q: ${value}`);
}
return value as TableId;
}
const t = tableId(5);
console.log(t); // 5Endi qoida: as TableId faqat tableId ichida. Qolgan kod TableId ni faqat shu funksiyadan oladi. Natijada TableId turi ikki narsani anglatadi: "bu stol raqami" va "u 1 dan 20 gacha butun son ekani tekshirilgan".
type TableId = number & { readonly __brand: "TableId" };
function tableId(value: number): TableId {
if (!Number.isInteger(value) || value < 1 || value > 20) {
throw new RangeError(`Bunday stol yo'q: ${value}`);
}
return value as TableId;
}
console.log(tableId(25));Konsolda:
RangeError: Bunday stol yo'q: 25Tur va tekshiruv birga ishlaydi: tsc "stol raqami o'rniga buyurtma raqami" xatosini ushlaydi, tableId esa "mavjud bo'lmagan stol" xatosini ishlash paytida ushlaydi.
3.1 Qachon tamg'a kerak
Har bir songa tamg'a bosish shart emas. Narxni Sum deb tamg'alasangiz, har bir + dan keyin uni qayta tamg'alashga to'g'ri keladi — kod og'irlashadi. Tamg'a uch holatda o'zini oqlaydi:
- Bir xil turdagi, lekin ma'nosi farqli qiymatlar yonma-yon yuradi —
TableIdvaOrderId,UserIdvaProductId. Ular funksiya parametrlarida tez-tez almashib qoladi. - Qiymat tekshiruvdan o'tgan bo'lishi shart — tekshirilgan email, tozalangan HTML, 1–20 oralig'idagi stol raqami. Tamg'a "tekshirilgan" degan guvohnoma bo'ladi.
- Birliklar aralashib ketishi mumkin — so'm va dollar, gramm va kilogramm, daqiqa va soniya.
Qolgan joylarda oddiy number va string yetadi. Tamg'a — aniq xavf bor joyga qo'yiladigan qulf, har eshikka emas.
3.2 Kutubxonalardagi yozuv
Kutubxonalarda tamg'a ko'pincha __brand satri o'rniga unique symbol bilan yoziladi — shunda tamg'a nomi boshqa hech qanday xususiyat bilan to'qnashmaydi. Mashqda shu usulni ko'rasiz.
4. Variance: tur munosabatlari qaysi yo'nalishda o'tadi
Endi darsning ikkinchi, chuqurroq qismi. Birinchi o'qishda hammasini tushunmasangiz — tabiiy; mashqlarga o'tib, keyin qaytsangiz bo'ladi.
4.1 Boshlang'ich munosabat
«Bahor» menyusida ichimliklar — taomlarning bir turi, faqat hajmi bor:
type MenuItem = { name: string; price: number };
type Drink = MenuItem & { volumeMl: number };
const tea: Drink = { name: "Ko'k choy", price: 5000, volumeMl: 500 };
const item: MenuItem = tea;
console.log(item.name); // Ko'k choyDrink ni MenuItem ga berish mumkin — unda kerakli hamma narsa bor. Buni qisqa yozamiz: Drink → MenuItem ("ichimlik — taom sifatida yaroqli"). Teskarisi yo'q: har taomda volumeMl yo'q.
Variance (o'zgaruvchanlik yo'nalishi) — savol: shu munosabat o'ralgan turlarga qanday o'tadi? Bu savol nazariy tuyulishi mumkin, lekin u har kuni uchraydi: ichimliklar ro'yxatini menyuni chizadigan funksiyaga berasiz, taom ishlovchisini ichimlik tugmasiga ulaysiz. Har safar kompilyator "bu mos keladimi?" deb so'raydi va javobni variance qoidalari beradi. Drink[] → MenuItem[] mi? (d: Drink) => void → (m: MenuItem) => void mi?
4.2 Kovariantlik: "o'qiladigan" joylar
Faqat beradigan (o'qiladigan) joylarda munosabat o'sha yo'nalishda o'tadi:
type MenuItem = { name: string; price: number };
type Drink = MenuItem & { volumeMl: number };
const drinks: readonly Drink[] = [
{ name: "Ko'k choy", price: 5000, volumeMl: 500 },
];
const items: readonly MenuItem[] = drinks;
const getDrink = (): Drink => drinks[0]!;
const getItem: () => MenuItem = getDrink;
console.log(items.length, getItem().name); // 1 Ko'k choy"Ichimliklar ro'yxati" — "taomlar ro'yxati" sifatida yaroqli: undan nima o'qisangiz ham, u taom. "Ichimlik beradigan funksiya" — "taom beradigan funksiya" sifatida yaroqli. Kovariantlik (covariance) — "birga o'zgaradi": Drink → MenuItem bo'lsa, readonly Drink[] → readonly MenuItem[] va () => Drink → () => MenuItem. (drinks[0]! — ! "bu undefined emas" degan non-null assertion; ro'yxatda bitta element borligini bilamiz.)
4.3 Kontravariantlik: parametrlar
Qabul qiladigan joyda — funksiya parametrida — munosabat teskari bo'ladi:
type MenuItem = { name: string; price: number };
type Drink = MenuItem & { volumeMl: number };
type Printer = { print: (item: MenuItem) => void };
const drinkPrinter = { print: (d: Drink) => console.log(d.volumeMl) };
const p: Printer = drinkPrinter;Kompilyator:
TS2322: Type '{ print: (d: Drink) => void; }' is not assignable to type 'Printer'.
Types of property 'print' are incompatible.
Type '(d: Drink) => void' is not assignable to type '(item: MenuItem) => void'.
Types of parameters 'd' and 'item' are incompatible.
Type 'MenuItem' is not assignable to type 'Drink'.
Property 'volumeMl' is missing in type 'MenuItem' but required in type '{ volumeMl: number; }'.Olti qatorli zanjir — sekin, pastdan o'qiymiz:
| Qator (pastdan) | Ma'nosi |
|---|---|
| 6 | MenuItem da volumeMl yo'q |
| 5 | demak MenuItem ni Drink ga berib bo'lmaydi |
| 4 | d va item parametrlari mos emas |
| 3 | demak (d: Drink) => void funksiyasi (item: MenuItem) => void o'rniga yaramaydi |
| 1–2 | demak drinkPrinter — Printer emas |
Mantiq: Printer "menga istalgan taom bering, chop etaman" deb va'da beradi. drinkPrinter esa faqat ichimlikni chop eta oladi — oshni bersangiz, volumeMl yo'q. Teskarisi xavfsiz: istalgan taomni chop eta oladigan funksiya ichimlikni ham chop etadi. Kontravariantlik (contravariance) — "teskari o'zgaradi": Drink → MenuItem bo'lsa, (m: MenuItem) => void → (d: Drink) => void.
| Joy | Yo'nalish | Nomi |
|---|---|---|
natija, readonly massiv/xususiyat |
Drink → MenuItem dagidek |
kovariant |
| funksiya parametri | teskari | kontravariant |
Ikki yo'nalishni bitta rasmda ko'ramiz — strelka "o'rniga yaraydi" degani:
flowchart LR
A["Drink"] -->|"mos"| B["MenuItem"]
C["readonly Drink[]"] -->|"kovariant"| D["readonly MenuItem[]"]
E["() => Drink"] -->|"kovariant"| F["() => MenuItem"]
G["(m: MenuItem) => void"] -->|"kontravariant"| H["(d: Drink) => void"]Birinchi uch qatorda strelka chapdan o'ngga — xuddi Drink → MenuItem dagidek. Oxirgi qatorda tartib teskari: MenuItem li funksiya chapda turibdi.
Kunlik o'xshatish: ish e'loni. "Ichimlik tayyorlay oladigan oshpaz kerak" degan joyga "har qanday taom tayyorlay oladigan" oshpaz yaraydi. "Har qanday taom" kerak bo'lgan joyga faqat choy damlay oladigan odam yaramaydi.
Shu qoida infer darsidagi bir savolga ham javob beradi. Bitta infer U ikki parametr o'rnida tutilsa, natija union emas, kesishma bo'ladi. Sababi — parametr kontravariant: ikkala funksiyaga ham beriladigan qiymat ikkala talabni birga bajarishi kerak, ya'ni A & B.
4.4 strictFunctionTypes
Parametrlarni bunday qat'iy tekshirish strictFunctionTypes bayrog'i bilan yoqiladi. U strict ga kiradi (Strict bayroqlar oilasi), shuning uchun bizda doim yoqiq. O'chirib ko'ramiz:
// @ts-options: {"strictFunctionTypes": false}
type MenuItem = { name: string; price: number };
type Drink = MenuItem & { volumeMl: number };
type Printer = { print: (item: MenuItem) => void };
const drinkPrinter = { print: (d: Drink) => console.log(d.volumeMl) };
const p: Printer = drinkPrinter;
console.log(typeof p.print); // functionXato yo'q. Bayroqsiz TypeScript parametrlarni ikki tomonlama (bivariant) tekshiradi: "Drink → MenuItem yoki MenuItem → Drink — bittasi bo'lsa yetadi". Bu TypeScript 2.6 gacha standart edi va eski kodlarda uchraydi.
Tekshirib ko'ring:
type Handler = (item: MenuItem) => void. Qaysi funksiyaniHandlerga berish mumkin:(d: Drink) => voidyoki(x: { name: string }) => void?
Javob
Ikkinchisini. Handler istalgan taomni beradi — (x: { name: string }) => void faqat name ni o'qiydi, u har taomda bor. Bu kontravariantlik: kengroq parametr qabul qiladigan funksiya torroq joyga yaraydi. (d: Drink) => void esa volumeMl kutadi — oshda u yo'q, strictFunctionTypes buni rad etadi.
5. TypeScript ataylab ko'z yumadigan ikki joy
Variance qoidalari to'liq qo'llanganda ko'p kod yozish og'irlashardi. TypeScript jamoasi ikki joyda qulaylikni tanlagan. Ikkalasi ham ishlash paytida yiqilishga olib keladi.
5.1 O'zgaruvchan massiv — kovariant (aslida xavfli)
readonly siz massiv ham Drink[] → MenuItem[] deb qabul qilinadi. Lekin oddiy massivga yozish ham mumkin:
type MenuItem = { name: string; price: number };
type Drink = MenuItem & { volumeMl: number };
const drinks: Drink[] = [
{ name: "Ko'k choy", price: 5000, volumeMl: 500 },
];
const items: MenuItem[] = drinks;
items.push({ name: "Osh", price: 35000 });
for (const d of drinks) {
console.log(d.volumeMl.toFixed(0));
}Konsolda:
500
TypeError: Cannot read properties of undefined (reading 'toFixed')items va drinks — bitta massiv (havola bo'yicha). items orqali unga osh qo'shildi, drinks esa uni ichimlik deb o'qidi. To'g'ri qoida bo'yicha o'zgaruvchan massiv invariant bo'lishi kerak edi ("faqat aynan o'sha tur"). TypeScript buni ataylab qilmaydi: aks holda Drink[] ni MenuItem[] qabul qiladigan hamma funksiyaga uzatib bo'lmasdi.
Himoya: massivni faqat o'qiydigan parametrlarni readonly qiling. readonly MenuItem[] ga push qilib bo'lmaydi — xavf yo'qoladi.
5.2 Metod sintaksisi — ikki tomonlama
strictFunctionTypes faqat funksiya xususiyatlariga (print: (item) => void) ta'sir qiladi. Metod sintaksisida (print(item): void) parametrlar hali ham ikki tomonlama tekshiriladi:
type MenuItem = { name: string; price: number };
type Drink = MenuItem & { volumeMl: number };
type Printer = { print(item: MenuItem): void };
const drinkPrinter = {
print(d: Drink) {
console.log(d.volumeMl.toFixed(0));
},
};
const p: Printer = drinkPrinter;
p.print({ name: "Osh", price: 35000 });Konsolda:
TypeError: Cannot read properties of undefined (reading 'toFixed')«Kontravariantlik» bo'limidagi bilan bir xil kod — faqat print: o'rniga print(…). tsc jim. Sabab tarixiy: DOM va standart kutubxonadagi ko'p turlar metod sintaksisida yozilgan, ularni qat'iy tekshirish minglab mavjud kodni buzardi.
Qoida: o'zingiz yozgan turlarda callback'larni xususiyat sintaksisida yozing: print: (item: MenuItem) => void. Shunda strictFunctionTypes ishlaydi.
6. in va out — kutubxona muallifi darajasi
TypeScript 4.7 dan beri generic parametr oldiga variance'ni qo'lda yozish mumkin:
type MenuItem = { name: string; price: number };
type Getter<out T> = () => T;
type Handler<in T> = (value: T) => void;
const getItem: Getter<MenuItem> = () => ({ name: "Osh", price: 1 });
const log: Handler<MenuItem> = (m) => console.log(m.name);
log(getItem()); // Oshout T— "Tfaqat chiqadi" (kovariant).in T— "Tfaqat kiradi" (kontravariant).
Odatda kompilyator variance'ni o'zi aniqlaydi va bu belgilar shart emas. Ular ikki holatda yoziladi: juda katta, murakkab turlarda kompilyatorni tezlashtirish uchun va hujjat sifatida. Belgini noto'g'ri qo'ysangiz, tsc ushlaydi:
type Getter<in T> = () => T;
type Handler<out T> = (value: T) => void;Kompilyator:
TS2636: Type 'Getter<super-T>' is not assignable to type 'Getter<sub-T>' as implied by variance annotation.
Type 'super-T' is not assignable to type 'sub-T'.
TS2636: Type 'Handler<sub-T>' is not assignable to type 'Handler<super-T>' as implied by variance annotation.
Types of parameters 'value' and 'value' are incompatible.
Type 'super-T' is not assignable to type 'sub-T'."Variance belgisi talab qilgan yo'nalishda Getter<super-T> ni Getter<sub-T> ga berib bo'lmaydi". super-T va sub-T — kompilyatorning o'ylab topilgan "kengroq T" va "torroq T" nomlari. Ma'nosi: in deb e'lon qildingiz, lekin T natijada — chiqish joyida turibdi. Bunday xabarni asosan kutubxona kodida uchratasiz.
7. Ko'p uchraydigan xatolar
7.1 Taxallusni yangi tur deb o'ylash
type OrderId = number — OrderId va number bir xil. Tuzatish: tamg'a — number & { readonly __brand: "OrderId" }.
7.2 Oddiy sonni tamg'ali joyga berish
TS2345: Argument of type 'number' is not assignable to parameter of type 'OrderId'. — bu xato foydali: tekshiruvsiz son kirmoqchi. Tuzatish: orderId(7) — tekshiruvchi funksiya orqali; 7 as OrderId emas.
7.3 as ni hamma joyda yozish
Har fayl o'z as OrderId ini yozsa, tamg'a hech narsani kafolatlamaydi. Tuzatish: as faqat bitta "eshik" funksiyada.
7.4 Tamg'ani ishlash paytida izlash
id.__brand — turga ko'ra bor, haqiqatda undefined. JSON'dan kelgan son ham tamg'asiz. Tuzatish: tashqi ma'lumotni eshik funksiyadan o'tkazing: orderId(data.id).
7.5 Callback'ni metod sintaksisida yozish
print(item: MenuItem): void — strictFunctionTypes ishlamaydi. Tuzatish: print: (item: MenuItem) => void.
8. Mashqlar
1-mashq (oson): Qaysi biri o'tadi?
type Kg = number & { readonly __brand: "Kg" };
const kg = (n: number) => n as Kg;
const rice: Kg = kg(2);
const plain: number = rice;
console.log(plain * 1000); // 2000rice ni number ga berish mumkinmi (ha/yo'q)? [:ha]. const r: Kg = 2; — tsc xato beradimi? [:ha]. typeof rice ishlash paytida qanday satr?
Yechim
Tamg'ali tur oddiy turga mos keladi — Kg kesishmasida number bor. Oddiy 2 esa tamg'asiz — TS2322. Ishlash paytida tamg'a yo'q, typeof rice — "number".
2-mashq (o'rta): Variance'ni bashorat qiling
type MenuItem = { name: string; price: number }, type Drink = MenuItem & { volumeMl: number }. Qaysilari tsc dan o'tadi (strict bilan)?
const a: readonly MenuItem[] = drinksList;(drinksList: readonly Drink[])const b: (d: Drink) => void = (m: MenuItem) => {};const c: (m: MenuItem) => void = (d: Drink) => {};const d: () => Drink = () => osh;(osh: MenuItem)
Yechim
type MenuItem = { name: string; price: number };
type Drink = MenuItem & { volumeMl: number };
const drinksList: readonly Drink[] = [];
const a: readonly MenuItem[] = drinksList;
const b: (d: Drink) => void = (m: MenuItem) => console.log(m.name);
console.log(a.length, typeof b); // 0 functionBirinchi va ikkinchisi o'tadi. Birinchisi — kovariant: ichimliklar ro'yxati taomlar ro'yxati sifatida o'qiladi. Ikkinchisi — kontravariant: istalgan taomni qabul qiladigan funksiya ichimlikni ham qabul qiladi. Uchinchisi — xato: faqat ichimlikni qabul qiladigan funksiyaga osh kelishi mumkin. To'rtinchisi ham xato: osh ichimlik emas (volumeMl yo'q), natija esa kovariant — yo'nalish noto'g'ri.
3-mashq (qiyin): Amaliy tajriba — stol va buyurtma raqamlari
kurs/mashqlar/15/31-brand/ids.ts faylini yarating:
Brand<T, B>— umumiy tamg'a turi,unique symbolbilan (pastdagi ishora).TableId,OrderId—Brand<number, …>.tableId(value)— 1–20 butun son, aks holdaRangeError;orderId(value)— musbat butun son.Order—{ readonly id: OrderId; readonly table: TableId }.ordersAt(orders, table)— shu stolning buyurtma raqamlari.
Ishora: faylning boshida declare const brand: unique symbol; qatori — "brand degan, boshqa hech narsaga teng bo'lmagan symbol bor deb hisobla". declare — "qiymat boshqa joyda, menga faqat turini ayt" degani (uni Declaration fayllari darsida o'rganamiz). Bu qator JavaScript'ga o'tmaydi — haqiqiy symbol yaratilmaydi, u faqat tamg'a kaliti sifatida turda yashaydi.
Testlar yozing va uchinchi testning oxiriga (}); dan oldin) ataylab ordersAt(orders, orderId(5)); va ordersAt(orders, 5); qatorlarini qo'shib, npx tsc --pretty false ni ishga tushiring.
Yechim
// 31-brand/ids.ts — stol va buyurtma raqamlari adashmasin
declare const brand: unique symbol;
export type Brand<T, B extends string> = T & { readonly [brand]: B };
export type TableId = Brand<number, "TableId">;
export type OrderId = Brand<number, "OrderId">;
// Yagona "eshik": tekshiruvdan o'tgan son tamg'alanadi
export function tableId(value: number): TableId {
if (!Number.isInteger(value) || value < 1 || value > 20) {
throw new RangeError(`Bunday stol yo'q: ${value}`);
}
return value as TableId;
}
export function orderId(value: number): OrderId {
if (!Number.isInteger(value) || value < 1) {
throw new RangeError(`Buyurtma raqami noto'g'ri: ${value}`);
}
return value as OrderId;
}
export type Order = { readonly id: OrderId; readonly table: TableId };
export function ordersAt(
orders: readonly Order[],
table: TableId,
): OrderId[] {
return orders.filter((o) => o.table === table).map((o) => o.id);
}[brand]: B — kalit nomi "__brand" satri emas, symbol. Hech bir haqiqiy obyektda bunday kalit bo'lmaydi va u boshqa kutubxonaning __brand i bilan to'qnashmaydi.
// 31-brand/ids.test.ts
import { test } from "node:test";
import assert from "node:assert/strict";
import { tableId, orderId, ordersAt } from "./ids.ts";
import type { Order } from "./ids.ts";
const orders: Order[] = [
{ id: orderId(101), table: tableId(5) },
{ id: orderId(102), table: tableId(3) },
{ id: orderId(103), table: tableId(5) },
];
test("ordersAt: stol bo'yicha buyurtmalar", () => {
assert.deepEqual(ordersAt(orders, tableId(5)), [101, 103]);
assert.deepEqual(ordersAt(orders, tableId(7)), []);
});
test("tableId: 1–20 oralig'idan tashqari — RangeError", () => {
assert.throws(() => tableId(0), RangeError);
assert.throws(() => tableId(21), RangeError);
assert.throws(() => tableId(2.5), RangeError);
});
test("brend ishlash paytida yo'q: oddiy son", () => {
const id = orderId(101);
assert.equal(typeof id, "number");
assert.equal(JSON.stringify({ id }), '{"id":101}');
});Ataylab qo'shilgan ikki qatorga tsc shunday dedi:
31-brand/ids.test.ts(28,20): error TS2345: Argument of type 'OrderId' is not assignable to parameter of type 'TableId'.
Type 'OrderId' is not assignable to type '{ readonly [brand]: "TableId"; }'.
Types of property '[brand]' are incompatible.
Type '"OrderId"' is not assignable to type '"TableId"'.
31-brand/ids.test.ts(29,20): error TS2345: Argument of type 'number' is not assignable to parameter of type 'TableId'.
Type 'number' is not assignable to type '{ readonly [brand]: "TableId"; }'.«Nega bu kerak?» bo'limidagi xato endi kodga yetib bormaydi. Qatorlarni olib tashlagach, npx tsc --pretty false — chiqishsiz. node --test 31-brand/ids.test.ts bizda (Node 24.21.0):
✔ ordersAt: stol bo'yicha buyurtmalar (2.9307ms)
✔ tableId: 1–20 oralig'idan tashqari — RangeError (0.9525ms)
✔ brend ishlash paytida yo'q: oddiy son (0.4443ms)
ℹ tests 3
ℹ suites 0
ℹ pass 3
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 299.351Uchinchi test darsning asosiy fikrini avtomatik tasdiqlaydi: tamg'a ishlash paytida yo'q, JSON'ga ham tushmaydi.
XATOLAR.md ga qo'shing: TS2345 ning tamg'ali ko'rinishi (__brand/[brand] mos emas), TS2636 (variance belgisi), TS2322 ning parametrlar zanjiri ("Types of parameters … are incompatible"). Keyin kurs/mashqlar papkasidan:
git add 15/31-brand 15/XATOLAR.md
git commit -m "15/31: ids.ts — TableId va OrderId tamg'ali turlar"9. Real ishda
- Identifikatorlar:
UserId,OrderId,ProductId— katta loyihalarda eng ko'p tamg'alanadigan turlar. Ma'lumotlar bazasi bilan ishlashda (24-qismdan keyin) "foydalanuvchi ID o'rniga buyurtma ID" xatosi ayniqsa qimmat. - Birliklar va tekshirilgan qiymatlar:
Sum(so'm),Kg,Email(tekshirilgan email),SafeHtml(tozalangan HTML — XSS himoyasi, 12/13-qismlardagi mavzu): tamg'a "bu qiymat tekshiruvdan o'tgan" degani. - Kutubxonalar: Zod'da
.brand()metodi bor (Zod darsida),type-festdaTaggedturi (best practices darsida). Ular aynan shu g'oyani tayyor beradi. - Variance — kutubxona muallifi darajasi. Ilova kodida siz variance'ni deyarli yozmaysiz, lekin
Types of parameters … are incompatiblexabarini o'qishingiz kerak.in/outbelgilarini React va boshqa katta kutubxonalarning turlarida uchratasiz. - Intervyu: "Nominal va strukturaviy tiplash farqi?", "TypeScript'da nominal turni qanday qilasiz?", "Kovariantlik va kontravariantlik nima?" — yuqori darajadagi TypeScript intervyularida.
Xulosa
- TypeScript strukturaviy:
type OrderId = number— yangi tur emas, taxallus. - Tamg'a —
number & { readonly __brand: "OrderId" }: shakl farqi faqat turda, ishlash paytida oddiy son. - Tamg'ani bitta tekshiruvchi funksiya qo'yadi (
asfaqat o'sha yerda) — tur "tekshirilgan" degan ma'noni ham oladi. - Natija va
readonlyjoylar kovariant, parametrlar kontravariant;strictFunctionTypesikkinchisini tekshiradi. - Ikki teshik: o'zgaruvchan massiv kovariant va metod sintaksisi ikki tomonlama —
readonlyva xususiyat sintaksisi bilan yoping.
Keyingi dars: Rekursiv turlar va tur murakkabligi chegaralari — o'z-o'zini chaqiradigan turlar (DeepPartial, JSONValue) va kompilyator qachon "yetadi" deydi.
Manbalar
- TypeScript Handbook: "Type Compatibility" (Function Parameter Bivariance) — typescriptlang.org/docs/handbook/type-compatibility.html
- TypeScript Release Notes 2.6 (
strictFunctionTypes, metodlar istisnosi) va 4.7 (Optional Variance Annotations —in/out) — typescriptlang.org/docs/handbook/release-notes/ - TSConfig:
strictFunctionTypes— typescriptlang.org/tsconfig#strictFunctionTypes
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!