Türkçe

TypeScript'in güçlü Mapped Types ve Conditional Types özelliklerine yönelik kapsamlı bir rehber; sağlam ve tip güvenli uygulamalar için pratik örnekler ve gelişmiş kullanım senaryoları içerir.

TypeScript'in Mapped Types ve Conditional Types Konularında Uzmanlaşmak

JavaScript'in bir üst kümesi olan TypeScript, sağlam ve sürdürülebilir uygulamalar oluşturmak için güçlü özellikler sunar. Bu özellikler arasında, Mapped Types (Eşlenmiş Tipler) ve Conditional Types (Koşullu Tipler), gelişmiş tip manipülasyonu için temel araçlar olarak öne çıkar. Bu rehber, söz dizimlerini, pratik uygulamalarını ve gelişmiş kullanım senaryolarını keşfederek bu kavramlara kapsamlı bir genel bakış sunar. İster deneyimli bir TypeScript geliştiricisi olun, ister yolculuğunuza yeni başlıyor olun, bu makale sizi bu özellikleri etkili bir şekilde kullanma bilgisiyle donatacaktır.

Mapped Types (Eşlenmiş Tipler) Nedir?

Eşlenmiş Tipler, mevcut tipleri dönüştürerek yeni tipler oluşturmanıza olanak tanır. Mevcut bir tipin özellikleri üzerinde yinelenir ve her bir özelliğe bir dönüşüm uygularlar. Bu, özellikle tüm özellikleri isteğe bağlı veya salt okunur yapmak gibi mevcut tiplerin varyasyonlarını oluşturmak için kullanışlıdır.

Temel Söz Dizimi

Bir Eşlenmiş Tip için söz dizimi aşağıdaki gibidir:

type NewType<T> = {
  [K in keyof T]: Transformation;
};

Pratik Örnekler

Özellikleri Salt Okunur (Read-Only) Yapma

Bir kullanıcı profilini temsil eden bir arayüzünüz (interface) olduğunu varsayalım:

interface UserProfile {
  name: string;
  age: number;
  email: string;
}

Tüm özelliklerin salt okunur olduğu yeni bir tip oluşturabilirsiniz:

type ReadOnlyUserProfile = {
  readonly [K in keyof UserProfile]: UserProfile[K];
};

Artık ReadOnlyUserProfile, UserProfile ile aynı özelliklere sahip olacak, ancak hepsi salt okunur olacaktır.

Özellikleri İsteğe Bağlı (Optional) Yapma

Benzer şekilde, tüm özellikleri isteğe bağlı yapabilirsiniz:

type OptionalUserProfile = {
  [K in keyof UserProfile]?: UserProfile[K];
};

OptionalUserProfile, UserProfile'ın tüm özelliklerine sahip olacak, ancak her özellik isteğe bağlı olacaktır.

Özellik Tiplerini Değiştirme

Ayrıca her bir özelliğin tipini de değiştirebilirsiniz. Örneğin, tüm özellikleri string olacak şekilde dönüştürebilirsiniz:

type StringifiedUserProfile = {
  [K in keyof UserProfile]: string;
};

Bu durumda, StringifiedUserProfile içindeki tüm özellikler string tipinde olacaktır.

Conditional Types (Koşullu Tipler) Nedir?

Koşullu Tipler, bir koşula bağlı olan tipler tanımlamanıza olanak tanır. Bir tipin belirli bir kısıtlamayı karşılayıp karşılamadığına dayalı olarak tip ilişkilerini ifade etmenin bir yolunu sunarlar. Bu, JavaScript'teki üçlü operatöre (ternary operator) benzer, ancak tipler için geçerlidir.

Temel Söz Dizimi

Bir Koşullu Tip için söz dizimi aşağıdaki gibidir:

T extends U ? X : Y

Pratik Örnekler

Bir Tipin String Olup Olmadığını Belirleme

Girdi tipi bir string ise string, aksi takdirde number döndüren bir tip oluşturalım:

type StringOrNumber<T> = T extends string ? string : number;

type Result1 = StringOrNumber<string>;  // string
type Result2 = StringOrNumber<number>;  // number
type Result3 = StringOrNumber<boolean>; // number

Bir Birleşimden (Union) Tip Çıkarma

Bir birleşim tipinden (union type) belirli bir tipi çıkarmak için koşullu tipleri kullanabilirsiniz. Örneğin, null olmayan (non-nullable) tipleri çıkarmak için:

type NonNullable<T> = T extends null | undefined ? never : T;

type Result4 = NonNullable<string | null | undefined>; // string

Burada, eğer T, null veya undefined ise, tip never olur, bu da TypeScript'in birleşim tipi basitleştirmesi tarafından filtrelenir.

Tipleri Çıkarımlama (Inferring)

Koşullu tipler, infer anahtar kelimesini kullanarak tipleri çıkarımlamak için de kullanılabilir. Bu, daha karmaşık bir tip yapısından bir tip çıkarmanıza olanak tanır.

type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;

function myFunction(x: number): string {
  return x.toString();
}

type Result5 = ReturnType<typeof myFunction>; // string

Bu örnekte, ReturnType bir fonksiyonun dönüş tipini çıkarır. T'nin herhangi bir argüman alan ve bir R tipi döndüren bir fonksiyon olup olmadığını kontrol eder. Eğer öyleyse, R'yi döndürür; aksi takdirde any döndürür.

Mapped Types ve Conditional Types'ı Birleştirme

Mapped Types ve Conditional Types'ın asıl gücü, onları birleştirmekten gelir. Bu, son derece esnek ve anlamlı tip dönüşümleri oluşturmanıza olanak tanır.

Örnek: Derinlemesine Salt Okunur (Deep Readonly)

Yaygın bir kullanım durumu, bir nesnenin iç içe geçmiş özellikler de dahil olmak üzere tüm özelliklerini salt okunur yapan bir tip oluşturmaktır. Bu, özyinelemeli (recursive) bir koşullu tip kullanılarak başarılabilir.

type DeepReadonly<T> = {
  readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K];
};

interface Company {
  name: string;
  address: {
    street: string;
    city: string;
  };
}

type ReadonlyCompany = DeepReadonly<Company>;

Burada, DeepReadonly, readonly değiştiricisini tüm özelliklere ve onların iç içe geçmiş özelliklerine özyinelemeli olarak uygular. Eğer bir özellik bir nesne ise, o nesne üzerinde özyinelemeli olarak DeepReadonly'yi çağırır. Aksi takdirde, sadece özelliğe readonly değiştiricisini uygular.

Örnek: Özellikleri Tipe Göre Filtreleme

Yalnızca belirli bir tipe sahip özellikleri içeren bir tip oluşturmak istediğinizi varsayalım. Bunu başarmak için Mapped Types ve Conditional Types'ı birleştirebilirsiniz.

type FilterByType<T, U> = {
  [K in keyof T as T[K] extends U ? K : never]: T[K];
};

interface Person {
  name: string;
  age: number;
  isEmployed: boolean;
}

type StringProperties = FilterByType<Person, string>; // { name: string; }

type NonStringProperties = Omit<Person, keyof StringProperties>;

Bu örnekte, FilterByType, T'nin özellikleri üzerinde yinelenir ve her özelliğin tipinin U'yu genişletip genişletmediğini kontrol eder. Eğer genişletiyorsa, özelliği sonuç tipine dahil eder; aksi takdirde, anahtarı never'a eşleyerek onu hariç tutar. Anahtarları yeniden eşlemek için "as" kullanımına dikkat edin. Daha sonra, string özelliklerini orijinal arayüzden kaldırmak için `Omit` ve `keyof StringProperties` kullanırız.

Gelişmiş Kullanım Senaryoları ve Desenler

Temel örneklerin ötesinde, Mapped Types ve Conditional Types, son derece özelleştirilebilir ve tip güvenli uygulamalar oluşturmak için daha gelişmiş senaryolarda kullanılabilir.

Dağılımsal Koşullu Tipler (Distributive Conditional Types)

Kontrol edilen tip bir birleşim tipi (union type) olduğunda, koşullu tipler dağılımsaldır. Bu, koşulun birleşimin her bir üyesine ayrı ayrı uygulandığı ve sonuçların daha sonra yeni bir birleşim tipinde birleştirildiği anlamına gelir.

type ToArray<T> = T extends any ? T[] : never;

type Result6 = ToArray<string | number>; // string[] | number[]

Bu örnekte, ToArray, string | number birleşiminin her bir üyesine ayrı ayrı uygulanır ve sonuç olarak string[] | number[] elde edilir. Eğer koşul dağılımsal olmasaydı, sonuç (string | number)[] olurdu.

Yardımcı Tipleri (Utility Types) Kullanma

TypeScript, Mapped Types ve Conditional Types'tan yararlanan birkaç yerleşik yardımcı tip sunar. Bu yardımcı tipler, daha karmaşık tip dönüşümleri için yapı taşları olarak kullanılabilir.

Bu yardımcı tipler, karmaşık tip manipülasyonlarını basitleştirebilen güçlü araçlardır. Örneğin, sadece belirli özellikleri isteğe bağlı yapan bir tip oluşturmak için Pick ve Partial'ı birleştirebilirsiniz:

type Optional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;

interface Product {
  id: number;
  name: string;
  price: number;
  description: string;
}

type OptionalDescriptionProduct = Optional<Product, "description">;

Bu örnekte, OptionalDescriptionProduct, Product'ın tüm özelliklerine sahiptir, ancak description özelliği isteğe bağlıdır.

Şablon Değişmez Değer Tipleri (Template Literal Types) Kullanma

Şablon Değişmez Değer Tipleri (Template Literal Types), string değişmez değerlerine dayalı tipler oluşturmanıza olanak tanır. Dinamik ve anlamlı tip dönüşümleri oluşturmak için Mapped Types ve Conditional Types ile birlikte kullanılabilirler. Örneğin, tüm özellik adlarının başına belirli bir string ekleyen bir tip oluşturabilirsiniz:

type Prefix<T, P extends string> = {
  [K in keyof T as `${P}${string & K}`]: T[K];
};

interface Settings {
  apiUrl: string;
  timeout: number;
}

type PrefixedSettings = Prefix<Settings, "data_">;

Bu örnekte, PrefixedSettings, data_apiUrl ve data_timeout özelliklerine sahip olacaktır.

En İyi Uygulamalar ve Dikkat Edilmesi Gerekenler

Sonuç

Mapped Types (Eşlenmiş Tipler) ve Conditional Types (Koşullu Tipler), son derece esnek ve anlamlı tip dönüşümleri oluşturmanızı sağlayan güçlü TypeScript özellikleridir. Bu kavramlarda uzmanlaşarak, TypeScript uygulamalarınızın tip güvenliğini, sürdürülebilirliğini ve genel kalitesini artırabilirsiniz. Özellikleri isteğe bağlı veya salt okunur yapmak gibi basit dönüşümlerden, karmaşık özyinelemeli dönüşümlere ve koşullu mantığa kadar, bu özellikler size sağlam ve ölçeklenebilir uygulamalar oluşturmak için gereken araçları sunar. Tam potansiyellerini ortaya çıkarmak ve daha yetkin bir TypeScript geliştiricisi olmak için bu özellikleri keşfetmeye ve denemeye devam edin.

TypeScript yolculuğunuza devam ederken, resmi TypeScript belgeleri, çevrimiçi topluluklar ve açık kaynaklı projeler de dahil olmak üzere mevcut zengin kaynaklardan yararlanmayı unutmayın. Mapped Types ve Conditional Types'ın gücünü benimseyin; en zorlu tip ile ilgili sorunların bile üstesinden gelmek için iyi donanımlı olacaksınız.