Discriminated union — tag field pattern

~20 daq · TypeScript · Type Narrowing

API javobi uch holat qaytaradi: yuklanmoqda, muvaffaqiyat, xato. Har birida turli xususiyatlar. in operator bilan ajratish — qaysi xususiyat noyob ekanini eslab qolish shart, yangi holat qo'shilganda unutish mumkin.

Discriminated Union — har ob'ektga bitta noyob "belgi" (tag) maydoni berish. TypeScript bu maydonga qarab barcha xususiyatlarni biladi.


Muammo — belgisiz union

type ApiHolat =
  | { yuklanmoqda: boolean }
  | { natija: string }
  | { xabar: string };

function qayta(h: ApiHolat) {
  if ("natija" in h) return h.natija;
  if ("xabar"  in h) return h.xabar;
  // yuklanmoqda holati unutilsa TypeScript ogohlantirmaydi
}

Discriminated union — yechim

Har variantda bir xil nomli, turli literal qiymatli maydon:

type ApiHolat =
  | { tur: "yuklanmoqda" }
  | { tur: "muvaffaqiyat"; natija: string }
  | { tur: "xato";         xabar: string  };

switch bilan to'liq va xavfsiz ajratish:

function qayta(h: ApiHolat): string {
  switch (h.tur) {
    case "yuklanmoqda":   return "Yuklanmoqda...";
    case "muvaffaqiyat":  return h.natija; // ✅ natija mavjud
    case "xato":          return h.xabar;  // ✅ xabar mavjud
  }
}

Agar "bekor" holati qo'shilsa va switch da ko'rilmasa — TypeScript string qaytishini talab qilib xato beradi.


To'lov tizimi misoli

type Tolov =
  | { tur: "karta";  raqam: string; muddat: string }
  | { tur: "naqd";   summa: number }
  | { tur: "online"; email: string };

function tolovBajar(t: Tolov): string {
  switch (t.tur) {
    case "karta":  return `Karta ${t.raqam}`;
    case "naqd":   return `Naqd ${t.summa} so'm`;
    case "online": return `PayPal ${t.email}`;
  }
}

t.tur === "karta" blokida TypeScript t.raqam va t.muddat mavjudligini biladi — boshqa holatlarda emas.


Generic API javob

type Javob<T> =
  | { status: "ok";    data:  T      }
  | { status: "xato";  xabar: string }
  | { status: "kutish"               };

function handle<T>(j: Javob<T>): T | null {
  switch (j.status) {
    case "ok":     return j.data;
    case "xato":   console.error(j.xabar); return null;
    case "kutish": return null;
  }
}

Tez-tez uchraydigan xatolar

Discriminant maydoni string (keng) tur:

// ❌ — string juda keng, TypeScript ajrata olmaydi
type A = { tur: string; x: number };
type B = { tur: string; y: string };

// ✅ — literal tur
type A = { tur: "a"; x: number };
type B = { tur: "b"; y: string };

switch da barcha case yo'q:

type Holat = { tur: "a" } | { tur: "b" } | { tur: "c" };

function fn(h: Holat): string {
  switch (h.tur) {
    case "a": return "A";
    case "b": return "B";
    // "c" unutildi — TypeScript xato beradi (string qaytishi kerak)
  }
}

Xulosa

// Har variantda noyob literal tag
type Shakl =
  | { tur: "doira";       radius: number        }
  | { tur: "to'rtburchak"; en: number; bo: number }
  | { tur: "uchburchak";  asos: number; h: number };

function maydoni(s: Shakl): number {
  switch (s.tur) {
    case "doira":        return Math.PI * s.radius ** 2;
    case "to'rtburchak": return s.en * s.bo;
    case "uchburchak":   return (s.asos * s.h) / 2;
  }
}

Tag maydoni + switch = to'liq, xavfsiz, kengaytiriladigan tuzilma.


Keyingi dars

31-dars: Type predicate (is) va exhaustiveness checking (never)