İçeriğe geç
TypeScript

TypeScript Mapped Types: Dinamik Tip Dönüşümleri

Tarık Tunç
TypeScript Mapped Types: Dinamik Tip Dönüşümleri

TypeScript Mapped Types: Dinamik Tip Dönüşümleri

Mapped type'lar, mevcut tiplerin özelliklerini döngüleyerek yeni tipler oluşturmanızı sağlar. TypeScript'in en güçlü tip sistemi özelliklerinden biridir ve Partial, Required, Readonly gibi utility type'ların temelini oluşturur. Bu yazıda mapped type'ları sıfırdan ileri seviyeye kadar inceleyeceğiz.

Mapped Type Temelleri

// Temel mapped type syntax
type MappedType<T> = {
  [K in keyof T]: T[K];
};

// Tüm özellikleri opsiyonel yapma (Partial'ın implementasyonu)
type Opsiyonel<T> = {
  [K in keyof T]?: T[K];
};

// Tüm özellikleri readonly yapma
type SaltOkunur<T> = {
  readonly [K in keyof T]: T[K];
};

// Tüm özellikleri zorunlu yapma
type Zorunlu<T> = {
  [K in keyof T]-?: T[K];  // -? opsiyonelliği kaldırır
};

// Readonly'yi kaldırma
type Yazilabilir<T> = {
  -readonly [K in keyof T]: T[K];
};

// Kullanım
interface Kullanici {
  readonly id: number;
  isim: string;
  email?: string;
}

type OpsiyonelKullanici = Opsiyonel<Kullanici>;
type ZorunluKullanici = Zorunlu<Kullanici>;
type YazilabilirKullanici = Yazilabilir<Kullanici>;

Key Remapping (as ile)

TypeScript 4.1 ile gelen key remapping, anahtar adlarını dönüştürmenizi sağlar:

// Getter tipi oluşturma
type Getters<T> = {
  [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};

interface Kisi {
  isim: string;
  yas: number;
}

type KisiGetters = Getters<Kisi>;
// { getIsim: () => string; getYas: () => number; }

// Belirli tipleri filtreleme
type SadeceFonksiyonlar<T> = {
  [K in keyof T as T[K] extends Function ? K : never]: T[K];
};

interface Karisik {
  isim: string;
  yas: number;
  selamla(): void;
  hesapla(x: number): number;
}

type SadeceMetotlar = SadeceFonksiyonlar<Karisik>;
// { selamla: () => void; hesapla: (x: number) => number; }

// Event handler tipleri
type EventHandlers<T> = {
  [K in keyof T as `on${Capitalize<string & K>}Change`]: (
    yeniDeger: T[K],
    eskiDeger: T[K]
  ) => void;
};

type KullaniciEventHandlers = EventHandlers<Kullanici>;
// {
//   onIdChange: (yeniDeger: number, eskiDeger: number) => void;
//   onIsimChange: (yeniDeger: string, eskiDeger: string) => void;
//   onEmailChange: ...
// }
İpucu: Key remapping'de never döndürmek, o anahtarı sonuç tipinden tamamen çıkarır. Bu, filtreleme işlemleri için çok kullanışlıdır.

Mapped Type ile Utility Type İmplementasyonları

// Pick implementasyonu
type MyPick<T, K extends keyof T> = {
  [P in K]: T[P];
};

// Omit implementasyonu
type MyOmit<T, K extends keyof T> = {
  [P in keyof T as P extends K ? never : P]: T[P];
};

// Record implementasyonu
type MyRecord<K extends keyof any, T> = {
  [P in K]: T;
};

// Readonly deep implementasyonu
type DeepReadonly<T> = {
  readonly [K in keyof T]: T[K] extends object
    ? T[K] extends Function
      ? T[K]
      : DeepReadonly<T[K]>
    : T[K];
};

// Partial deep implementasyonu
type DeepPartial<T> = {
  [K in keyof T]?: T[K] extends object
    ? T[K] extends Function
      ? T[K]
      : DeepPartial<T[K]>
    : T[K];
};

// Kullanım
interface Ayarlar {
  genel: {
    tema: string;
    dil: string;
    bildirimler: {
      email: boolean;
      push: boolean;
    };
  };
  profil: {
    isim: string;
    avatar: string;
  };
}

type KismiAyarlar = DeepPartial<Ayarlar>;
// Tüm iç içe özellikler de opsiyonel olur

Pratik Mapped Type Örnekleri

// Form doğrulama tipleri
type FormHatalari<T> = {
  [K in keyof T]?: string[];
};

type FormDokunuldu<T> = {
  [K in keyof T]: boolean;
};

interface KayitFormu {
  isim: string;
  email: string;
  sifre: string;
}

const hatalar: FormHatalari<KayitFormu> = {
  email: ["Geçerli bir email adresi girin"],
  sifre: ["En az 8 karakter olmalı", "Büyük harf içermeli"]
};

const dokunuldu: FormDokunuldu<KayitFormu> = {
  isim: true,
  email: true,
  sifre: false
};

// API endpoint tipleri
type CrudEndpoints<T extends Record<string, any>> = {
  [K in keyof T as `${string & K}Getir`]: (id: number) => Promise<T[K]>;
} & {
  [K in keyof T as `${string & K}Listele`]: () => Promise<T[K][]>;
} & {
  [K in keyof T as `${string & K}Olustur`]: (veri: Omit<T[K], "id">) => Promise<T[K]>;
} & {
  [K in keyof T as `${string & K}Sil`]: (id: number) => Promise<boolean>;
};

interface Modeller {
  kullanici: { id: number; isim: string };
  urun: { id: number; ad: string; fiyat: number };
}

type Api = CrudEndpoints<Modeller>;
// kullaniciGetir, kullaniciListele, kullaniciOlustur, kullaniciSil
// urunGetir, urunListele, urunOlustur, urunSil
Uyarı: Aşırı karmaşık mapped type'lar, TypeScript derleyicisinin performansını olumsuz etkileyebilir ve hata mesajlarını okunaksız hale getirebilir. Karmaşık tipleri küçük, anlamlı parçalara bölerek hem okunabilirliği hem de performansı artırın.
Bilgi: Mapped type'lar, TypeScript'in "tip seviyesinde programlama" yapmanızı sağlayan araçlarıdır. Conditional type'lar ve template literal type'lar ile birleştirildiğinde, çok karmaşık tip dönüşümleri ifade edebilirsiniz.

Sonuç

Mapped type'lar, TypeScript tip sisteminin en güçlü araçlarından biridir. Mevcut tiplerin özelliklerini dönüştürme, filtreleme ve yeniden adlandırma yeteneği ile kod tekrarını minimize edebilir ve tip güvenliğini artırabilirsiniz. Utility type'ların iç yapısını anlamak, kendi özel mapped type'larınızı oluşturmanızda size rehberlik edecektir.

Kaynaklar

İlgili Yazılar