Mundarija (29)
- Bu darsda
- 1. Nega bu kerak?
- 2. Discriminated union
- 2.1 Har holat — alohida variant
- 2.2 Imkonsiz holatlar endi yozilmaydi
- 2.3 kind ni tekshirmasdan — faqat umumiy maydon
- 2.4 kind bo'yicha toraytirish
- 3. switch va to'liqlik tekshiruvi
- 3.1 switch — har variantga bitta case
- 3.2 Bitta case unutilsa
- 3.3 never bilan aniq tekshiruv
- 3.4 assertNever yordamchisi
- 4. Holat mashinasi
- 4.1 Holatlar va o'tishlar
- 4.2 O'tish funksiyasi
- 5. TypeScript nimani ushlamaydi
- 6. Ko'p uchraydigan xatolar
- 6.1 Ajratuvchi maydon literal emas
- 6.2 Hamma maydonni ixtiyoriy qilish
- 6.3 default da standart qiymat va tekshiruvsiz
- 6.4 Natija turini yozmaslik
- 7. Mashqlar
- 1-mashq (oson): Variantni tanlang
- 2-mashq (o'rta): To'liqlik tekshiruvini qo'shing
- 3-mashq (qiyin): Holat mashinasi — to'liq
- 4-mashq: Vazifalar qadami — literal va discriminated union
- 8. Real ishda
- Xulosa
- Manbalar
TypeScript discriminated union va to'liqlik tekshiruvi: kind, switch va never
Qisqacha: Discriminated union (ajratuvchi maydonli union) — har varianti umumiy literal maydonga ega obyektlar union'i:
{ kind: "tayyor"; readyAt: string } | { kind: "bekor"; reason: string }. Ajratuvchini (kind) tekshirsangiz, TypeScript qaysi variant ekanini va unda qaysi maydonlar borligini biladi.switchningdefaultida qiymatnineverga bersangiz — to'liqlik tekshiruvi: yangi variant qo'shilib, uni unutsangiz,tscxato beradi.
Bu darsda
- "Imkonsiz holatlar"ni turlar bilan yo'qotasiz: ixtiyoriy maydonlar to'plami o'rniga variantlar.
- Ajratuvchi (discriminant) maydon bilan union'ni
ifvaswitchda toraytirasiz. neverbilan "hamma variant ko'rib chiqildi" tekshiruvini yozasiz vaassertNeveryordamchisini bilasiz.- Holat mashinasini (state machine) discriminated union bilan modellashtirasiz.
vazifalarga literal union'lar va birinchi ishlaydigan.tsmodulni qo'shasiz.
Oldin bilishingiz kerak: Narrowing: turni toraytirish, Union va literal turlar, switch.
1. Nega bu kerak?
«Bahor» buyurtmasi bir necha holatdan o'tadi: yangi → tayyorlanmoqda → tayyor → berildi. Ba'zan — bekor qilinadi. Sardor birinchi variantni bitta obyekt bilan yozdi:
type Order = {
status: "yangi" | "tayyorlanmoqda" | "tayyor" | "bekor";
cook?: string; // tayyorlanmoqda bo'lsa
readyAt?: string; // tayyor bo'lsa
reason?: string; // bekor bo'lsa
};
const strange: Order = { status: "bekor", readyAt: "19:40" };
console.log(strange.reason ?? "sabab yo'q"); // sabab yo'qtsc — xatosiz. Lekin bu buyurtma ma'nosiz: "bekor qilingan, sababi yo'q, lekin 19:40 da tayyor bo'lgan". Uchta ixtiyoriy maydon — sakkizta kombinatsiya, ularning ko'pi imkonsiz holat. Har joyda order.reason ?? …, order.cook! — va har biri unutilishi mumkin.
Muammo shundaki, tur "qaysi maydon qaysi holatda bo'ladi" degan bilimni saqlamaydi. Bu bilim faqat izohlarda. Bugun uni turga ko'chiramiz.
2. Discriminated union
2.1 Har holat — alohida variant
Har holatni o'z maydonlari bilan alohida obyekt turi qilib yozamiz:
type OrderState =
| { kind: "yangi" }
| { kind: "tayyorlanmoqda"; cook: string }
| { kind: "tayyor"; readyAt: string }
| { kind: "bekor"; reason: string };
const a: OrderState = { kind: "tayyorlanmoqda", cook: "Jasur aka" };
const b: OrderState = { kind: "bekor", reason: "mehmon ketdi" };
console.log(a.kind, b.kind); // tayyorlanmoqda bekorBirinchi qator boshidagi | — ixtiyoriy, faqat chiroy uchun: har variant alohida qatorda turadi.
Har variantda kind bor va uning turi — literal: "yangi", "tayyor". Bu umumiy maydon ajratuvchi (discriminant) deyiladi: uning qiymati qaysi variant ekanini bir ma'noli aytadi. Butun tur esa — discriminated union (ajratuvchi maydonli union). Maydon nomi istalgancha bo'lishi mumkin — kind, type, status, tur. Muhimi: hamma variantda bor va har birida boshqa literal.
Kundalik o'xshatish — pochta jo'natmasi: yorliqdagi "XAT", "POSILKA", "BANDEROL" so'zi qutini ochmasdan ichida nima borligini aytadi. Posilkada og'irlik bor, xatda — yo'q.
2.2 Imkonsiz holatlar endi yozilmaydi
«Nega bu kerak?» dagi ma'nosiz buyurtmalar:
type OrderState =
| { kind: "yangi" }
| { kind: "tayyorlanmoqda"; cook: string }
| { kind: "tayyor"; readyAt: string }
| { kind: "bekor"; reason: string };
const noReason: OrderState = { kind: "bekor" };
const mixed: OrderState = { kind: "tayyor", reason: "?" };Kompilyator:
TS2322: Type '{ kind: "bekor"; }' is not assignable to type 'OrderState'.
Property 'reason' is missing in type '{ kind: "bekor"; }' but required in type '{ kind: "bekor"; reason: string; }'.
TS2353: Object literal may only specify known properties, and 'reason' does not exist in type '{ kind: "tayyor"; readyAt: string; }'.Diqqat qiling: TypeScript kind ga qarab aynan qaysi variant bilan solishtirishni tanladi. Xabarlar butun OrderState haqida emas, "bekor" va "tayyor" variantlari haqida. "Bekor — sababsiz" ham, "tayyor — sabab bilan" ham endi yozib bo'lmaydi.
2.3 kind ni tekshirmasdan — faqat umumiy maydon
type OrderState =
| { kind: "yangi" }
| { kind: "tayyorlanmoqda"; cook: string }
| { kind: "tayyor"; readyAt: string }
| { kind: "bekor"; reason: string };
function why(state: OrderState) {
return state.reason;
}Kompilyator:
TS2339: Property 'reason' does not exist on type 'OrderState'.
Property 'reason' does not exist on type '{ kind: "yangi"; }'.Union darsidagi qoida: union bilan faqat umumiy narsa — bu yerda faqat kind. Ikkinchi qator birinchi "aybdor" variantni ko'rsatdi: yangi buyurtmada sabab yo'q.
2.4 kind bo'yicha toraytirish
O'tgan darsdagi tenglik bilan toraytirish:
type OrderState =
| { kind: "yangi" }
| { kind: "tayyorlanmoqda"; cook: string }
| { kind: "tayyor"; readyAt: string }
| { kind: "bekor"; reason: string };
function why(state: OrderState): string {
if (state.kind === "bekor") {
return state.reason;
}
return "bekor qilinmagan";
}
const cancelled: OrderState = { kind: "bekor", reason: "ketdi" };
console.log(why(cancelled)); // ketdistate.kind === "bekor" — union'dan faqat { kind: "bekor"; reason: string } qoldi, reason endi bor. in bilan ham bo'lardi ("reason in state"), lekin kind aniqroq: u har variantda bor va qiymatlari takrorlanmaydi.
3. switch va to'liqlik tekshiruvi
3.1 switch — har variantga bitta case
Variantlar ko'p bo'lsa, switch o'qishga qulayroq. Har case ichida tur o'sha variantga toraygan:
type OrderState =
| { kind: "yangi" }
| { kind: "tayyorlanmoqda"; cook: string }
| { kind: "tayyor"; readyAt: string }
| { kind: "bekor"; reason: string };
function label(state: OrderState): string {
switch (state.kind) {
case "yangi":
return "Navbatda";
case "tayyorlanmoqda":
return `Oshpaz: ${state.cook}`;
case "tayyor":
return `Tayyor: ${state.readyAt}`;
case "bekor":
return `Bekor: ${state.reason}`;
}
}
const ready: OrderState = { kind: "tayyor", readyAt: "19:40" };
console.log(label(ready)); // Tayyor: 19:40default yo'q, lekin tsc jim — natija turi string va hamma yo'l return bilan tugaydi. TypeScript to'rt case dan keyin state ni never deb biladi: "boshqa variant qolmadi".
3.2 Bitta case unutilsa
Jasur aka "berildi" holatini qo'shishni so'radi. Sardor uni turga qo'shdi, lekin label ni unutdi. Natija turi yozilgan bo'lsa, buni darhol bilamiz:
type OrderState =
| { kind: "yangi" }
| { kind: "tayyor"; readyAt: string }
| { kind: "bekor"; reason: string };
function label(state: OrderState): string {
switch (state.kind) {
case "yangi":
return "Navbatda";
case "tayyor":
return `Tayyor: ${state.readyAt}`;
}
}Kompilyator:
TS2366: Function lacks ending return statement and return type does not include 'undefined'.Bu xabar void darsida ko'rgan TS2366. TypeScript hisobladi: "bekor" holatida funksiya oxiriga yetib, hech narsa qaytarmaydi. Lekin xabar qaysi variant unutilganini aytmaydi. Va natija turi yozilmasa yoki funksiya hech narsa qaytarmasa (void), umuman jim qoladi.
3.3 never bilan aniq tekshiruv
Ishonchli usul — default da qiymatni never turli o'zgaruvchiga berish:
type OrderState =
| { kind: "yangi" }
| { kind: "tayyor"; readyAt: string }
| { kind: "bekor"; reason: string };
function label(state: OrderState): string {
switch (state.kind) {
case "yangi":
return "Navbatda";
case "tayyor":
return `Tayyor: ${state.readyAt}`;
default: {
const unhandled: never = state;
return unhandled;
}
}
}Kompilyator:
TS2322: Type '{ kind: "bekor"; reason: string; }' is not assignable to type 'never'.Endi xabar aniq: "{ kind: "bekor"; … } ni never ga berib bo'lmaydi" — ya'ni default ga hali "bekor" varianti yetib kelyapti. Hamma case yozilsa, default da state — never, va never ni never ga berish mumkin: xato yo'qoladi.
Bu — to'liqlik tekshiruvi (exhaustiveness check). G'oya o'tgan darslarda aytilgan edi: never ga hech qanday qiymat sig'maydi. Agar default ga biror qiymat yetib kelsa — bu ko'rib chiqilmagan variant.
flowchart TD
A["state: yangi | tayyor | bekor"] --> B{"case yangi"}
B -- "yo'q" --> C{"case tayyor"}
C -- "yo'q" --> D["default: state — bekor"]
D --> E{"const x: never = state"}
E --> F["TS2322: bekor ≠ never<br/>case yozilmagan!"]
B -- "ha" --> R1["return"]
C -- "ha" --> R2["return"]3.4 assertNever yordamchisi
Har switch da const …: never yozish o'rniga, ko'p loyihalarda kichik yordamchi bor:
function assertNever(value: never): never {
throw new Error(`Kutilmagan holat: ${JSON.stringify(value)}`);
}
type Size = "yarim" | "to'liq";
function portion(size: Size): number {
switch (size) {
case "yarim":
return 0.6;
case "to'liq":
return 1;
default:
return assertNever(size);
}
}
console.log(portion("yarim")); // 0.6U ikki ish qiladi. Kompilyatsiyada: argument never bo'lishi shart — unutilgan variant TS2345: Argument of type '…' is not assignable to parameter of type 'never'. beradi. Ishlash paytida: agar qandaydir yo'l bilan begona qiymat kelib qolsa, jim o'tib ketmasdan aniq xabar bilan xato tashlaydi. Oddiy literal union'larda ham (Size) ishlaydi.
Tekshirib ko'ring:
type Payment = "naqd" | "karta" | "click";vaswitchda faqat"naqd"va"karta"uchuncasebor,default: return assertNever(p);.tscnima deydi?
Javob
TS2345: Argument of type '"click"' is not assignable to parameter of type 'never'. — default ga "click" yetib keldi. Xabar unutilgan variantni nomi bilan aytdi: unga case "click": qo'shish kerak.
4. Holat mashinasi
4.1 Holatlar va o'tishlar
Buyurtma holatlari tasodifiy almashmaydi: "berildi" dan "yangi" ga qaytib bo'lmaydi, "bekor" — oxirgi holat. Bunday tizim holat mashinasi (state machine) deyiladi: chekli holatlar va ular orasidagi ruxsat etilgan o'tishlar.
stateDiagram-v2
[*] --> yangi
yangi --> tayyorlanmoqda: oshpaz oldi
yangi --> bekor: mehmon ketdi
tayyorlanmoqda --> tayyor: pishdi
tayyor --> berildi: stolga
berildi --> [*]
bekor --> [*]Discriminated union holatlarni yaxshi ifodalaydi: har holat o'z ma'lumoti bilan (cook, readyAt, reason). O'tishlar esa funksiya bo'ladi: "joriy holat + hodisa → yangi holat".
4.2 O'tish funksiyasi
type OrderState =
| { kind: "yangi" }
| { kind: "tayyorlanmoqda"; cook: string }
| { kind: "tayyor"; readyAt: string };
function startCooking(state: OrderState, cook: string): OrderState {
if (state.kind !== "yangi") {
throw new Error(`"${state.kind}" holatida boshlab bo'lmaydi`);
}
return { kind: "tayyorlanmoqda", cook };
}
function finish(state: OrderState, time: string): OrderState {
if (state.kind !== "tayyorlanmoqda") {
throw new Error(`"${state.kind}" holatida tugatib bo'lmaydi`);
}
return { kind: "tayyor", readyAt: time };
}
const cooking = startCooking({ kind: "yangi" }, "Jasur aka");
const ready = finish(cooking, "19:40");
console.log(ready); // { kind: 'tayyor', readyAt: '19:40' }Har funksiya yangi holatni yangi obyekt sifatida qaytaradi — eski holat o'zgarmaydi. Noto'g'ri o'tish (finish({ kind: "yangi" }, …)) ishlash paytida xato beradi. Turlar esa har holat to'g'ri ma'lumot bilan yaratilishini kafolatlaydi: { kind: "tayyor" } ni readyAt siz qaytarib bo'lmaydi.
5. TypeScript nimani ushlamaydi
To'liqlik tekshiruvi — kompilyatsiya paytida. Tashqaridan kelgan holatni u ko'rmaydi:
type OrderState =
| { kind: "yangi" }
| { kind: "tayyor"; readyAt: string };
function assertNever(value: never): never {
throw new Error(`Kutilmagan holat: ${JSON.stringify(value)}`);
}
function label(state: OrderState): string {
switch (state.kind) {
case "yangi":
return "Navbatda";
case "tayyor":
return `Tayyor: ${state.readyAt}`;
default:
return assertNever(state);
}
}
const fromServer: OrderState = JSON.parse('{"kind":"arxiv"}');
console.log(label({ kind: "yangi" }));
console.log(label(fromServer));Konsolda:
Navbatda
Error: Kutilmagan holat: {"kind":"arxiv"}tsc — xatosiz: JSON.parse — any. Serverning yangi versiyasi "arxiv" holatini qo'shgan, ilova esa uni bilmaydi. assertNever hech bo'lmasa xatoni aniq qildi. const x: never = state; return x; usulida esa funksiya jim { kind: "arxiv" } obyektini qaytarardi — satr o'rniga obyekt. Ikki yo'l bor: tashqi ma'lumotni kirishda tekshirish (API ma'lumotlarini tiplash) yoki default da xavfsiz standart qiymat qaytarish (vazifalar dagi tartibla kabi — «Vazifalar qadami» da).
6. Ko'p uchraydigan xatolar
6.1 Ajratuvchi maydon literal emas
type A = { kind: string; … } — kind oddiy string bo'lsa, u hech narsani ajratmaydi: if (x.kind === "bekor") toraytirmaydi. Tuzatish: har variantda aniq literal: kind: "bekor".
6.2 Hamma maydonni ixtiyoriy qilish
{ status; cook?; readyAt?; reason? } — imkonsiz holatlar ochiq qoladi. Tuzatish: har holat — alohida variant.
6.3 default da standart qiymat va tekshiruvsiz
default: return "Noma'lum"; — yangi variant qo'shilsa, u jim "Noma'lum" bo'lib chiqadi. Tuzatish: default da never tekshiruvi yoki assertNever.
6.4 Natija turini yozmaslik
Natija turi yozilmasa, TS2366 ham chiqmaydi — funksiya natijasi string | undefined bo'lib qoladi. Tuzatish: natija turini yozing va never tekshiruvini qo'shing.
7. Mashqlar
1-mashq (oson): Variantni tanlang
type Seat =
| { kind: "stol"; number: number }
| { kind: "vip"; room: string }
| { kind: "olib_ketish" };
function seatText(seat: Seat): string {
switch (seat.kind) {
case "stol":
return `Stol ${seat.number}`;
case "vip":
return `VIP: ${seat.room}`;
case "olib_ketish":
return "Olib ketish";
}
}
const room: Seat = { kind: "vip", room: "Bahor-1" };
console.log(seatText(room)); // VIP: Bahor-1Ajratuvchi maydonning nomi: [:kind]. case "stol" ichida seat.room ni o'qisak, xato kodi: TS[:2339]. { kind: "olib_ketish", number: 5 } xatosining kodi: TS.
Yechim
- Ajratuvchi —
kind: hamma variantda bor, har birida boshqa literal. case "stol"ichidaseat—{ kind: "stol"; number: number }, undaroomyo'q:TS2339.olib_ketishvariantidanumberyo'q va obyekt yangi literal — ortiqcha xususiyat:TS2353.
2-mashq (o'rta): To'liqlik tekshiruvini qo'shing
Kodga default va never tekshiruvini qo'shing. Keyin Seat ga { kind: "teras"; table: number } variantini qo'shib, tsc qaysi qatorda va nima deyishini aniqlang. Oxirida terasni ham ko'rib chiqing.
Yechim
teras qo'shilgach, case siz holatda tsc:
TS2322: Type '{ kind: "teras"; table: number; }' is not assignable to type 'never'.Xato — default dagi const unhandled: never = seat; qatorida. Tuzatilgan to'liq variant:
type Seat =
| { kind: "stol"; number: number }
| { kind: "vip"; room: string }
| { kind: "teras"; table: number }
| { kind: "olib_ketish" };
function seatText(seat: Seat): string {
switch (seat.kind) {
case "stol":
return `Stol ${seat.number}`;
case "vip":
return `VIP: ${seat.room}`;
case "teras":
return `Teras, stol ${seat.table}`;
case "olib_ketish":
return "Olib ketish";
default: {
const unhandled: never = seat;
return unhandled;
}
}
}
console.log(seatText({ kind: "teras", table: 3 })); // Teras, stol 3default endi hech qachon bajarilmaydi — seat u yerda never. Lekin u kelajak uchun "qo'riqchi": keyingi yangi variant ham shu qatorda ushlanadi.
3-mashq (qiyin): Holat mashinasi — to'liq
«Holat mashinasi» bo'limidagi sxema bo'yicha OrderState ni beshta holat bilan yozing (berildi — maydonsiz). Hodisalar ham discriminated union: { type: "boshlash"; cook: string }, { type: "tugatish"; time: string }, { type: "berish" }, { type: "bekor_qilish"; reason: string }. next(state, event) funksiyasi sxemadagi o'tishlarni bajarsin. Ruxsat etilmagan o'tishda — holat o'zgarmasin. state.kind bo'yicha switch to'liq bo'lsin (never bilan).
Yechim
type OrderState =
| { kind: "yangi" }
| { kind: "tayyorlanmoqda"; cook: string }
| { kind: "tayyor"; readyAt: string }
| { kind: "berildi" }
| { kind: "bekor"; reason: string };
type OrderEvent =
| { type: "boshlash"; cook: string }
| { type: "tugatish"; time: string }
| { type: "berish" }
| { type: "bekor_qilish"; reason: string };
function next(state: OrderState, event: OrderEvent): OrderState {
switch (state.kind) {
case "yangi":
if (event.type === "boshlash") {
return { kind: "tayyorlanmoqda", cook: event.cook };
}
if (event.type === "bekor_qilish") {
return { kind: "bekor", reason: event.reason };
}
return state;
case "tayyorlanmoqda":
return event.type === "tugatish"
? { kind: "tayyor", readyAt: event.time }
: state;
case "tayyor":
return event.type === "berish" ? { kind: "berildi" } : state;
case "berildi":
case "bekor":
return state; // oxirgi holatlar
default: {
const unhandled: never = state;
return unhandled;
}
}
}
let order: OrderState = { kind: "yangi" };
order = next(order, { type: "boshlash", cook: "Jasur aka" });
// ruxsat yo'q — holat o'zgarmaydi:
order = next(order, { type: "berish" });
order = next(order, { type: "tugatish", time: "19:40" });
order = next(order, { type: "berish" });
console.log(order); // { kind: 'berildi' }case "berildi": case "bekor":— ikkicasebitta blokda (switchdagi "tushib o'tish"): ikkalasi ham oxirgi holat.event.type === "boshlash"dan keyinevent.cookishlatildi — hodisa ham toraydi. Ikki union bir funksiyada, har biri o'z ajratuvchisi bilan.berishhodisasitayyorlanmoqdaholatida e'tiborsiz qoldi: oshxonada hali tayyor bo'lmagan taomni stolga berib bo'lmaydi.
4-mashq: Vazifalar qadami — literal va discriminated union
vazifalar da filtr, saralash, tarmoq holati va xato kodlari hali oddiy string. Ruxsat etilgan qiymatlar faqat izohda: @param {string} filtr "hammasi" | "faol" | "bajarilgan". Bu qadamda ular turlar.ts ga union bo'lib ko'chadi (Union va literal turlar darsining va'dasi), tablar orasidagi xabar — discriminated union bo'ladi, tartibla esa switch va to'liqlik tekshiruviga o'tadi. Ilovada birinchi marta ishlaydigan .ts modul paydo bo'ladi: sinxron.ts.
- Branch:
refactor/union-turlar - Commit:
refactor: literal va discriminated union, to'liqlik tekshiruvi (never), tanasi:sinxron.ts; Filtr, Saralash, xato kodlari; tartibla switch
turlar.tsga:Filtr,Saralash,TarmoqHolati,VazifaXatosiKodi,ApiXatosiKodi,SinxronXabar.royxat.js,saralash.js,vazifa.js,api.js—stringo'rniga yangi turlar (JSDoc@param {Filtr}va h.k.).saralash.js—tartiblaifzanjiri o'rnigaswitchvaneverbilan.sinxron.js→sinxron.ts: kelgan xabarunknown, tekshirilgandan keyinSinxronXabar.asosiy.jsuni"./sinxron.ts"deb import qiladi.tsconfig.json—rewriteRelativeImportExtensions;sw.js—VERSIYA = "v5-2";sw.test.js—.tsmanbalarni ham tanisin.
Yechim
turlar.ts ga qo'shilgan turlar:
// Literal turlar union'i: qiymat faqat shulardan biri. Ro'yxatlar
// (FILTRLAR, SARALASHLAR) ham shu turda — biri o'zgarsa, ikkinchisi
// tsc xatosi bilan eslatadi
export type Filtr = "hammasi" | "faol" | "bajarilgan";
export type Saralash = "standart" | "yangi" | "alifbo";
export type TarmoqHolati = "yuklanmoqda" | "tayyor" | "xato" | "oflayn";
/** VazifaXatosi.kod: import/kesh matni nega rad etildi */
export type VazifaXatosiKodi =
"JSON" | "TUZILMA" | "VERSIYA" | "VAZIFA" | "NOMALUM";
/** ApiXatosi.kod: so'rov nega bajarilmadi */
export type ApiXatosiKodi =
| "TARMOQ" // internet yo'q, DNS, CORS
| "VAQT" // 8 s ichida javob kelmadi
| "BEKOR" // AbortController.abort()
| "TOPILMADI" // 404
| "NOTOGRI" // 400, 422
| "SERVER" // 5xx
| "HTTP" // boshqa xato status
| "JAVOB" // tana JSON emas
| "NOMALUM";
/**
* Tablar orasidagi xabar (BroadcastChannel). Discriminated union:
* `tur` maydoni qaysi variant ekanini aytadi. Hozir bitta variant;
* yangisi qo'shilsa, uni qabul qiluvchi switch'dagi never tekshiruvi
* "bu turni ishlamading" deb tsc xatosi beradi
*/
export type SinxronXabar = {
readonly tur: "ozgardi";
readonly paket: string; // eksportMatni() natijasi
};Izohdagi ro'yxatlar endi tur: ApiXatosiKodi dagi har qiymat yonida u qachon chiqishi yozilgan. VazifaXatosi va ApiXatosi klasslarida kod maydoni JSDoc bilan shu turni oldi (/** @type {ApiXatosiKodi} */), holatKodi funksiyasi esa @returns {ApiXatosiKodi}. Endi return "TOPILMAD" kabi imlo xatosi tsc dan o'tmaydi.
SinxronXabar — hozircha bitta variantli discriminated union. Nega tur maydoni kerak, agar variant bitta bo'lsa? Ertaga ikkinchi xabar turi qo'shiladi (masalan, "chiqish"). Ajratuvchi maydon oldindan bo'lsa, qabul qiluvchi kod switch (xabar.tur) bilan yoziladi va yangi variant to'liqlik tekshiruvidan o'tadi. Bu switch TypeScript va DOM qadamida asosiy modulida paydo bo'ladi.
tartibla — switch va never. saralash.js hali JavaScript, shuning uchun never JSDoc bilan yoziladi — /** @type {never} */ — const qolmadi: never ning izohdagi shakli:
export function tartibla(vazifalar, saralash) {
switch (saralash) {
case "standart":
return vazifalar.toSorted(faollarOldin);
case "yangi":
// Bir nechta kalit: birinchisi 0 (teng) bo'lsa, || keyingisiga o'tadi
return vazifalar.toSorted(
(a, b) => faollarOldin(a, b) || yangilarOldin(a, b),
);
case "alifbo":
return alifboTartibida(vazifalar);
default: {
// To'liqlik tekshiruvi: Saralash'ga yangi qiymat qo'shilib, bu
// yerda case yozilmasa — saralash never emas, tsc xato beradi.
// Ish vaqtida esa (JS'dan begona satr kelsa) — "standart"
/** @type {never} */
const qolmadi = saralash;
void qolmadi;
return vazifalar.toSorted(faollarOldin);
}
}
}void qolmadi; — "o'zgaruvchi ishlatildi" deb belgilash (ESLint "ishlatilmagan o'zgaruvchi" demasin). Blokning oxirgi qatori — «TypeScript nimani ushlamaydi» bo'limidagi ikkinchi yo'l: JavaScript'dan begona satr kelsa (tartibla(v, "boshqa")), ilova yiqilmaydi, "standart" tartibni qo'llaydi. v4.1 ning testi aynan shuni tekshiradi va o'tadi.
Tekshiruv ishlashini sinadik: Saralash ga vaqtincha | "muddat" qo'shildi, case yozilmadi:
assets/js/saralash.js(61,13): error TS2322: Type '"muddat"' is not assignable to type 'never'.Bu darsning «never bilan aniq tekshiruv» bo'limidagi xabar — faqat JavaScript faylda, JSDoc orqali. Qo'shimcha qiymat olib tashlandi, xato 0.
sinxron.ts — birinchi ishlaydigan .ts modul:
// sinxron.ts — bir saytning tablari orasida xabar almashish
import type { SinxronXabar } from "./turlar.ts";
const kanal = new BroadcastChannel("vazifalar");
// Xabar turi: yuboruvchi ham, qabul qiluvchi ham shu nomni ishlatadi.
// const — turi "ozgardi" (literal), string emas
export const OZGARDI = "ozgardi";
export function tarqat(xabar: SinxronXabar): void {
kanal.postMessage(xabar);
}
// Boshqa tabda ilovaning eski yoki yangi versiyasi ochiq bo'lishi
// mumkin: kelgan ma'lumot — unknown. Bilmagan xabar jim tashlanadi
function xabarmi(malumot: unknown): malumot is SinxronXabar {
if (
typeof malumot !== "object" ||
malumot === null ||
!("tur" in malumot)
) {
return false;
}
return (
malumot.tur === OZGARDI &&
"paket" in malumot &&
typeof malumot.paket === "string"
);
}
export function tingla(ishlovchi: (xabar: SinxronXabar) => void): void {
kanal.addEventListener("message", (event) => {
if (xabarmi(event.data)) {
ishlovchi(event.data);
}
});
}Bu faylda deyarli hamma o'tgan darslar bor:
import type— faqat tur import qilinadi, build'da bu qator o'chadi (verbatimModuleSyntax,typevainterfaceqadami).const OZGARDI = "ozgardi"— literal tur"ozgardi", shuning uchuntarqat({ tur: OZGARDI, … })SinxronXabarga mos (Union va literal turlar).event.data—any, uniunknownparametrga beramiz (anyvaunknown).typeof,=== null,"tur" in malumot— ketma-ket toraytirish (Narrowing).ishlovchi: (xabar: SinxronXabar) => void— callback turi (Funksiyalarni tiplash).malumot is SinxronXabar— tur predikati: "trueqaytarsam, bu qiymatSinxronXabar". JSDoc'da@returns {qiymat is …}shaklida ko'rgansiz (12/39),.tsdagi to'liq qoidalari — keyingi darsda.
Ilovada kichik xulq farqi bor: buzuq xabar (paket satr emas) endi jim tashlanadi. Oldin u paketniOqi ga yetib, console.warn chiqarardi. Ilovaning o'zi bunday xabar yubormaydi — farq faqat begona yoki eski versiyali tabda seziladi.
JavaScript .ts ni import qiladi. asosiy.js da bitta qator: import { OZGARDI, tarqat, tingla } from "./sinxron.ts";. Brauzer esa .ts ni bilmaydi — dist/ da .js kerak. tsconfig.json ga yangi bayroq:
// import "./sinxron.ts" — fayl nomi aynan; build .js ga almashtiradi
"rewriteRelativeImportExtensions": true,Build'dan keyin dist/assets/js/asosiy.js da "./sinxron.js" turibdi — tekshirildi. Testlar (Node) esa manbani o'qiydi, u yerda .ts fayl haqiqatan bor. Shu sabab importlarda fayl nomi aynan yoziladi.
sw.test.js — 2 ta test tahrirlandi. "Har bir modul qobiqda" testi assets/js dagi .js fayllarni sanardi. Endi u .ts manbalarni ham .js nomi bilan qidiradi (sinxron.ts → sinxron.js), turlar.ts va .d.ts esa tashqarida — ular brauzerga yuklanmaydi. "Qobiqdagi fayl diskda bor" testi .js yoki .ts ni qabul qiladi. Sinab ko'rdik: assets/js/x.ts qo'shilsa — AssertionError: assets/js/x.js qobiqda yo'q. v4 testlari ichida tahrirlangani faqat shu ikkitasi.
sw.js da VERSIYA = "v5-2" — build natijasi o'zgardi, kesh yangilanishi kerak.
Tekshiruv: npm test — 137/137, tip, lint, format:check — toza. 159 ta brauzer tekshiruvi dist/ da — 159/159, tablar sinxroni bilan.
Commit va PR:
git switch -c refactor/union-turlar
# turlar.ts, sinxron.ts, saralash.js, JSDoc turlari, tsconfig, sw
npm run build && npm test && npm run tip
npm run lint && npm run format:check
git add .
git commit -m "refactor: literal va discriminated union, to'liqlik tekshiruvi (never)" \
-m "sinxron.ts; Filtr, Saralash, xato kodlari; tartibla switch"
git push -u origin refactor/union-turlar
gh pr create --fill
gh pr merge --mergeSarlavha — 70 belgi (≤ 72). Diff: 11 fayl, +144 −52.
8. Real ishda
- So'rov holati — eng mashhur discriminated union:
{ status: "loading" } | { status: "success"; data: T } | { status: "error"; error: Error }. React'da (17-qismdan) va TanStack Query kabi kutubxonalarda aynan shu shakl. - Redux va reducer'lar — "holat + hodisa → yangi holat" funksiyasi, hodisalar esa
{ type: "…" }union'i. Bizningnextfunksiyamiz — reducer'ning o'zi. - To'liqlik tekshiruvi — katta kod bazalarida yangi holat qo'shishning xavfsiz yo'li:
tsco'zgartirish kerak bo'lgan hamma joyni ro'yxat qilib beradi. - Intervyu: "Discriminated union nima?", "Exhaustiveness checking qanday ishlaydi?", "Nega ixtiyoriy maydonlar o'rniga union?" — o'rta darajadagi TypeScript intervyusining asosiy savollari.
Xulosa
- Discriminated union — umumiy literal maydonli (
kind,type,tur) obyektlar union'i. Har variantda faqat o'ziga tegishli maydonlar — imkonsiz holatlar yozilmaydi. if (x.kind === "…")vaswitch (x.kind)union'ni aniq variantga toraytiradi.- To'liqlik tekshiruvi:
defaultdaconst unhandled: never = xyokiassertNever(x)— unutilgan variantTS2322/TS2345beradi. - Holat mashinasi: holatlar — discriminated union, o'tishlar — "holat + hodisa → yangi holat" funksiyasi.
- Tashqi ma'lumot to'liqlik tekshiruvini chetlab o'tadi — kirishda tekshiring yoki
defaultda xavfsiz xulq bering.
Keyingi dars: Custom type guard va assertion funksiyalar — x is T va asserts x is T bilan o'z tekshiruv funksiyalaringizni yozasiz.
Manbalar
- TypeScript Handbook: "Narrowing" → Discriminated unions, The never type, Exhaustiveness checking — typescriptlang.org/docs/handbook/2/narrowing.html#discriminated-unions
- TSConfig:
rewriteRelativeImportExtensions(TypeScript 5.7) — typescriptlang.org/tsconfig#rewriteRelativeImportExtensions - MDN:
BroadcastChannel— developer.mozilla.org/docs/Web/API/BroadcastChannel
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!