TypeScript'te generics, tip güvenliğini kod tekrarına feda etmeden yeniden kullanılabilir soyutlamalar yazmanın temel yoludur. Bu rehber, temel generic sözdizimini bildiğini varsayar ve ileri seviye tip tekniklerine odaklanır: koşullu tipler, eşlenmiş tipler, infer, şablon değişmez (template literal) tipleri ve dağıtımsal (distributive) davranış. Kapsam TypeScript 4.x ve sonrasıyla uyumludur; burada kullanılan özelliklerin tamamı kararlı sürümlerde mevcuttur. Örnekler amaçlı olarak küçük ve kendi kendine yeterlidir; derleyici davranışına dair ifadeler dilin belgelenmiş kurallarına dayanır, belirli bir projede ölçülmüş sonuç iddiası taşımaz.
Önkoşullar ve kapsam
- Değişken tipler, fonksiyon imzaları ve birleşim (union) tipleriyle rahat çalışıyor olmalısın.
interfacevetypearasındaki farkı biliyor olman beklenir.strictmodun açık olduğunu varsayıyoruz.strictNullCheckskapalıyken aşağıdaki tekniklerin bir kısmı beklenen daraltmayı üretmez.- Sürüm notu:
inferiçinde birden fazla çıkarım, değişken tuple tipleri ve şablon değişmez tipleri gibi özellikler TypeScript 4.0 ve sonrasında kararlıdır. Daha eski sürümlerde bazı örnekler derlenmez.
1. Generic kısıtları doğru kurmak
Kısıt (extends), bir tip parametresinin kabul edeceği değer kümesini daraltır. Amaç yalnızca hatayı erken yakalamak değil, gövde içinde hangi üyelere erişebileceğini derleyiciye bildirmektir.
function pluck<T, K extends keyof T>(items: T[], key: K): T[K][] {
return items.map((item) => item[key]);
}
const users = [
{ id: 1, name: "Ada" },
{ id: 2, name: "Linus" },
];
const names = pluck(users, "name"); // string[]
// pluck(users, "email"); // Hata: "email" T'nin anahtarı değil
Beklenen davranış: K, T'nin anahtarlarına kısıtlandığı için geçersiz anahtar derleme zamanında reddedilir; dönüş tipi seçilen anahtarın değer tipinden türetilir.
Yaygın hata: Kısıtı keyof T yerine string yapmak. Bu, anahtar doğrulamasını kaybettirir ve dönüş tipini any ya da geniş bir birleşime düşürür.
2. Varsayılan tip parametreleri
Varsayılanlar, çağıran tarafın tip argümanını atlamasına izin verirken API'yi okunur kılar.
type ApiResponse<TData, TError = Error> =
| { ok: true; data: TData }
| { ok: false; error: TError };
type UserResponse = ApiResponse<{ id: number }>;
UserResponse, hata kolunda Error kullanır. Varsayılanı unknown yapmak, hata işleme kodunu her çağrıda daraltmaya zorlayacağı için genelde daha güvenli ama daha zahmetli bir tercihtir; seçim, hatayı kimin şekillendirdiğine bağlıdır.
3. Koşullu tipler ve dağıtımsal davranış
Koşullu tip T extends U ? X : Y biçimindedir. Kritik ayrıntı: T çıplak bir tip parametresi olduğunda koşullu tip dağıtımsal çalışır; birleşim üyeleri tek tek değerlendirilir.
type ToArray<T> = T extends unknown ? T[] : never;
type A = ToArray<string | number>; // string[] | number[]
Dağıtımı kapatmak için tip parametresini bir tuple içine al:
type ToArrayNonDist<T> = [T] extends [unknown] ? T[] : never;
type B = ToArrayNonDist<string | number>; // (string | number)[]
Yaygın hata: never üzerinde dağıtım. ToArray<never> sonucu never olur, çünkü boş birleşim üzerinde dağıtım boş kalır. Bir aracın never girdisinde beklenmedik şekilde never döndürmesi genelde bu kuraldan kaynaklanır.
4. infer ile tip çıkarma
infer, koşullu tipin desen eşleme kısmında bilinmeyen bir tipi bağlar.
type ElementType<T> = T extends (infer U)[] ? U : never;
type E1 = ElementType<string[]>; // string
type E2 = ElementType<number>; // never
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
type P = UnwrapPromise<Promise<number>>; // number
type Q = UnwrapPromise<string>; // string
Dönüş tipini çıkarmak için fonksiyon imzasını desenleyebilirsin:
type Return<T> = T extends (...args: never[]) => infer R ? R : never;
Burada never[] kullanmak, parametre tiplerini umursamadan herhangi bir fonksiyon imzasını eşleştirir. Parametreleri de çıkarmak istiyorsan infer P ile bir tuple bağlayabilirsin.
5. Eşlenmiş tipler ve değiştiriciler
Eşlenmiş tipler, mevcut bir tipin üyelerini dönüştürür. readonly ve ? değiştiricileri kaldırılabilir veya eklenebilir.
type Mutable<T> = {
-readonly [K in keyof T]: T[K];
};
type Required2<T> = {
[K in keyof T]-?: T[K];
};
Anahtar yeniden eşleme (as) ile üye adlarını dönüştürebilirsin:
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
type Point = { x: number; y: number };
type PointGetters = Getters<Point>;
// { getX: () => number; getY: () => number }
string & K ifadesi, K'nin string | number | symbol olabileceği durumda Capitalize'ın yalnızca string kabul etmesini karşılamak için gerekir.
Yaygın hata: Eşlenmiş tipte keyof T yerine elle yazılmış anahtar listesi kullanmak. Bu, T değiştiğinde tipin sessizce eskimesine yol açar.
6. Şablon değişmez tipleriyle çalışmak
Şablon değişmez tipleri, dizge sabitlerini tip düzeyinde ayrıştırmanı sağlar.
type EventName = `on${Capitalize<"click" | "focus">}`;
// "onClick" | "onFocus"
type PathParams<T extends string> =
T extends `${string}:${infer Param}/${infer Rest}`
? Param | PathParams<`/${Rest}`>
: T extends `${string}:${infer Param}`
? Param
: never;
type Params = PathParams<"/users/:id/posts/:postId">;
// "id" | "postId"
Beklenen davranış: özyineleme, son bölüt işlenene kadar sürer; hiç parametre yoksa never döner.
Dikkat: Derin özyineleme, derleyici örnekleme sınırına takılabilir. Şablon tipi özyinelemelerini küçük ve sınırlı desenlerde tutmak, "Type instantiation is excessively deep" hatasını önler.
7. Güvenlik ve doğruluk etkileri
- Çalışma zamanı doğrulaması yerine geçmez. Generics ve ileri tipler yalnızca derleme zamanında çalışır. Dış kaynaktan gelen JSON'u
as ApiResponse<User>ile tiplendirmek, veriyi doğrulamaz. Sınırda (ağ,localStorage, form girdisi) bir çalışma zamanı doğrulayıcı kullan; tip iddiasını buna dayandır. - Aşırı geniş
anysızıntısı. Bir yardımcı tipteanykullanmak, kısıt zinciri boyunca tip güvenliğini sessizce yok eder. Ara tiplerdeunknowntercih et; daraltmayı açıkça yap. asile zorlama.asoperatörü, derleyicinin reddettiği bir atamayı zorlar. İleri tiplerdeaskullanımı arttıkça, tip sisteminin sağladığı garantiler azalır. Herasbir inceleme noktasıdır.
8. Sık yapılan hatalar özeti
- Koşullu tipin dağıtımsal davranışını unutup birleşim girdilerinde beklenmedik sonuç almak; gerektiğinde
[T] extends [U]ile dağıtımı kapatmamak. keyof Tyerine geniş tip kısıtı kullanıp anahtar doğrulamasını kaybetmek.- Aracı tipleri
nevergirdisine karşı test etmemek. - Şablon tipi özyinelemesini sınırsız bırakıp derleyici derinlik sınırına çarpmak.
- Tip düzeyindeki garantiyi çalışma zamanı doğrulaması sanmak.
Sonuç
İleri seviye TypeScript tipleri, küçük ve birleştirilebilir araçlardan oluşur: kısıtlar, koşullu tipler, infer, eşlenmiş tipler ve şablon değişmezleri. Bu araçları tek tek anlamak, bunları birleştirip okunur bir tip mimarisi kurmaktan daha kolaydır. Pratikte en çok işe yarayan alışkanlık, her araç tipini never, birleşim ve bilinmeyen girdilerle sınamak ve sınır katmanında çalışma zamanı doğrulamasını tip iddiasından ayrı tutmaktır.