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;
};
T
: Üzerinde eşleme yapmak istediğiniz girdi tipidir.K in keyof T
: Girdi tipi olanT
'deki her bir anahtar üzerinde yinelenir.keyof T
,T
'deki tüm özellik adlarının bir birleşimini (union) oluşturur veK
, yineleme sırasında her bir anahtarı temsil eder.Transformation
: Her bir özelliğe uygulamak istediğiniz dönüşümdür. Bu, bir değiştirici (readonly
veya?
gibi) eklemek, tipi değiştirmek veya tamamen başka bir şey olabilir.
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
T
: Kontrol edilen tiptir.U
:T
tarafından genişletilen tiptir (koşul).X
: EğerT
,U
'yu genişletiyorsa (koşul doğruysa) döndürülecek tiptir.Y
: EğerT
,U
'yu genişletmiyorsa (koşul yanlışsa) döndürülecek tiptir.
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.
Partial<T>
:T
'nin tüm özelliklerini isteğe bağlı yapar.Required<T>
:T
'nin tüm özelliklerini zorunlu yapar.Readonly<T>
:T
'nin tüm özelliklerini salt okunur yapar.Pick<T, K>
:T
'den bir diziK
özelliği seçer.Omit<T, K>
:T
'den bir diziK
özelliği kaldırır.Record<K, T>
:T
tipinde bir diziK
özelliği olan bir tip oluşturur.Exclude<T, U>
:T
'denU
'ya atanabilen tüm tipleri hariç tutar.Extract<T, U>
:T
'denU
'ya atanabilen tüm tipleri çıkarır.NonNullable<T>
:T
'dennull
veundefined
'ı hariç tutar.Parameters<T>
: BirT
fonksiyon tipinin parametrelerini alır.ReturnType<T>
: BirT
fonksiyon tipinin dönüş tipini alır.InstanceType<T>
: BirT
kurucu fonksiyon tipinin örnek tipini (instance type) alır.
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
- Basit Tutun: Mapped Types ve Conditional Types güçlü olsalar da, kodunuzu daha karmaşık hale getirebilirler. Tip dönüşümlerinizi olabildiğince basit tutmaya çalışın.
- Yardımcı Tipleri Kullanın: Mümkün olduğunda TypeScript'in yerleşik yardımcı tiplerinden yararlanın. Bunlar iyi test edilmiştir ve kodunuzu basitleştirebilir.
- Tiplerinizi Belgeleyin: Özellikle karmaşık olan tip dönüşümlerinizi açıkça belgeleyin. Bu, diğer geliştiricilerin kodunuzu anlamasına yardımcı olacaktır.
- Tiplerinizi Test Edin: Tip dönüşümlerinizin beklendiği gibi çalıştığından emin olmak için TypeScript'in tip kontrolünü kullanın. Tiplerinizin davranışını doğrulamak için birim testleri yazabilirsiniz.
- Performansı Göz Önünde Bulundurun: Karmaşık tip dönüşümleri, TypeScript derleyicinizin performansını etkileyebilir. Tiplerinizin karmaşıklığına dikkat edin ve gereksiz hesaplamalardan kaçının.
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.