İçeriğe geç
TypeScript

TypeScript Union ve Intersection Types

Tarık Tunç
TypeScript Union ve Intersection Types

TypeScript Union ve Intersection Types

Union ve intersection type'lar, TypeScript'in tip sisteminin en güçlü yapı taşlarındandır. Mevcut tipleri birleştirerek yeni ve daha ifade gücü yüksek tipler oluşturmanızı sağlarlar. Bu yazıda her iki kavramı detaylı örneklerle inceleyeceğiz.

Union Types (Birleşim Tipleri)

Union type, bir değerin birden fazla tipten biri olabileceğini ifade eder. Pipe (|) operatörü ile tanımlanır:

// Basit union type
type StringVeyaSayi = string | number;

function formatla(deger: StringVeyaSayi): string {
  if (typeof deger === "string") {
    return deger.toUpperCase();
  }
  return deger.toFixed(2);
}

formatla("merhaba"); // "MERHABA"
formatla(3.14159);   // "3.14"

// Literal union type
type Yon = "kuzey" | "guney" | "dogu" | "bati";
type Zar = 1 | 2 | 3 | 4 | 5 | 6;
type Durum = "aktif" | "pasif" | "askida";

// Fonksiyonda union parametre
function mesajGoster(mesaj: string | string[]): void {
  if (Array.isArray(mesaj)) {
    mesaj.forEach(m => console.log(m));
  } else {
    console.log(mesaj);
  }
}

Discriminated Unions

Discriminated union, her tipin ortak bir "ayırt edici" (discriminant) özelliğe sahip olduğu union pattern'idir:

// Şekil hesaplama örneği
interface Daire {
  tur: "daire";
  yaricap: number;
}

interface Dikdortgen {
  tur: "dikdortgen";
  genislik: number;
  yukseklik: number;
}

interface Ucgen {
  tur: "ucgen";
  taban: number;
  yukseklik: number;
}

type Sekil = Daire | Dikdortgen | Ucgen;

function alanHesapla(sekil: Sekil): number {
  switch (sekil.tur) {
    case "daire":
      return Math.PI * sekil.yaricap ** 2;
    case "dikdortgen":
      return sekil.genislik * sekil.yukseklik;
    case "ucgen":
      return (sekil.taban * sekil.yukseklik) / 2;
  }
}

// Exhaustive check pattern
function alanHesaplaGuvenli(sekil: Sekil): number {
  switch (sekil.tur) {
    case "daire":
      return Math.PI * sekil.yaricap ** 2;
    case "dikdortgen":
      return sekil.genislik * sekil.yukseklik;
    case "ucgen":
      return (sekil.taban * sekil.yukseklik) / 2;
    default:
      const _exhaustiveCheck: never = sekil;
      return _exhaustiveCheck;
  }
}
İpucu: Discriminated union'larda never tipini kullanarak exhaustive check yapabilirsiniz. Yeni bir tip eklendiğinde switch/case'e eklenmezse TypeScript derleme hatası verir. Bu, tip güvenliğini en üst düzeyde tutar.

Intersection Types (Kesişim Tipleri)

Intersection type, birden fazla tipi birleştirerek tüm özellikleri içeren yeni bir tip oluşturur. Ampersand (&) operatörü ile tanımlanır:

// Temel intersection
interface Kisi {
  isim: string;
  yas: number;
}

interface Calisan {
  sirket: string;
  pozisyon: string;
  maas: number;
}

type CalisanKisi = Kisi & Calisan;

const gelistirici: CalisanKisi = {
  isim: "Tarik Tunç",
  yas: 28,
  sirket: "Tech Corp",
  pozisyon: "Senior Developer",
  maas: 50000
};

// Mixin pattern
interface Timestamped {
  createdAt: Date;
  updatedAt: Date;
}

interface SoftDeletable {
  deletedAt: Date | null;
  silindi: boolean;
}

interface Versionable {
  version: number;
  lastModifiedBy: string;
}

type BaseEntity = Timestamped & SoftDeletable & Versionable;

interface Makale extends BaseEntity {
  id: number;
  baslik: string;
  icerik: string;
  yazar: string;
}

Union ve Intersection Birlikte

// API yanıt tipleri
interface BasariliYanit<T> {
  basarili: true;
  data: T;
  mesaj: string;
}

interface HataYanit {
  basarili: false;
  hataKodu: number;
  mesaj: string;
}

type ApiYanit<T> = BasariliYanit<T> | HataYanit;

// Intersection ile genişletilmiş union
type PaginatedYanit<T> = ApiYanit<T> & {
  sayfa: number;
  toplamSayfa: number;
  toplamKayit: number;
};

async function kullanicilariGetir(): Promise<ApiYanit<Kullanici[]>> {
  try {
    const response = await fetch("/api/kullanicilar");
    const data = await response.json();
    return { basarili: true, data, mesaj: "Başarılı" };
  } catch (error) {
    return { basarili: false, hataKodu: 500, mesaj: "Sunucu hatası" };
  }
}

// Kullanım
const sonuc = await kullanicilariGetir();
if (sonuc.basarili) {
  console.log(sonuc.data); // Kullanici[] - TypeScript bunu bilir!
} else {
  console.error(sonuc.hataKodu); // number - TypeScript bunu bilir!
}
Uyarı: Intersection type'larda çakışan özellikler never tipine dönüşür. Örneğin, { x: string } & { x: number } ifadesinde x özelliği never olur çünkü bir değer hem string hem number olamaz.

İleri Seviye Kullanım Senaryoları

// Conditional rendering tipleri
type ButtonProps =
  | { variant: "link"; href: string; onClick?: never }
  | { variant: "button"; onClick: () => void; href?: never }
  | { variant: "submit"; formId: string; onClick?: never; href?: never };

function Button(props: ButtonProps) {
  switch (props.variant) {
    case "link":
      return `<a href="${props.href}">Link</a>`;
    case "button":
      return `<button>Button</button>`;
    case "submit":
      return `<button type="submit" form="${props.formId}">Submit</button>`;
  }
}

// State machine pattern
type OdemeDurumu =
  | { durum: "bekleniyor" }
  | { durum: "isleniyor"; islemId: string }
  | { durum: "basarili"; islemId: string; tarih: Date }
  | { durum: "basarisiz"; islemId: string; hata: string };

function odemeDurumMesaji(odeme: OdemeDurumu): string {
  switch (odeme.durum) {
    case "bekleniyor":
      return "Ödeme bekleniyor...";
    case "isleniyor":
      return `İşlem ${odeme.islemId} işleniyor...`;
    case "basarili":
      return `İşlem ${odeme.islemId} başarıyla tamamlandı.`;
    case "basarisiz":
      return `İşlem ${odeme.islemId} başarısız: ${odeme.hata}`;
  }
}
Bilgi: Discriminated union'lar, state machine pattern'ini tip seviyesinde modellemek için mükemmeldir. Her durum için farklı özelliklere sahip nesneler tanımlayarak, geçersiz durumları derleme zamanında yakalarsınız.

Sonuç

Union ve intersection type'lar, TypeScript'in tip sisteminin temel yapı taşlarıdır. Union type'lar "A veya B" mantığını, intersection type'lar ise "A ve B" mantığını temsil eder. Discriminated union pattern'i ile tip güvenli state yönetimi yapabilir, intersection ile mixin'ler oluşturabilirsiniz. Bu iki kavramı ustalaştığınızda TypeScript tip sistemi ile karmaşık iş mantığını güvenle modelleyebilirsiniz.

Kaynaklar

İlgili Yazılar