TypeScript İleri Seviye Generic Tipler Rehberi

TypeScript'te generics, tip güvenliğini kod tekrarına feda etmeden yeniden kullanılabilir soyutlamalar yazmanın temel yoludur. Bu rehber, temel generi...

TypeScript İleri Seviye Generic Tipler Rehberi

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.
  • interface ve type arasındaki farkı biliyor olman beklenir.
  • strict modun açık olduğunu varsayıyoruz. strictNullChecks kapalıyken aşağıdaki tekniklerin bir kısmı beklenen daraltmayı üretmez.
  • Sürüm notu: infer iç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ş any sızıntısı. Bir yardımcı tipte any kullanmak, kısıt zinciri boyunca tip güvenliğini sessizce yok eder. Ara tiplerde unknown tercih et; daraltmayı açıkça yap.
  • as ile zorlama. as operatörü, derleyicinin reddettiği bir atamayı zorlar. İleri tiplerde as kullanımı arttıkça, tip sisteminin sağladığı garantiler azalır. Her as bir 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 T yerine geniş tip kısıtı kullanıp anahtar doğrulamasını kaybetmek.
  • Aracı tipleri never girdisine 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.

İLGİLİ İÇERİKLER

Bunları da Okumak İsteyebilirsiniz