İçindekiler
TypeScript’in tip sistemi yalnızca string veya number yazıp hataları erkenden yakalamaktan ibaret değildir. Conditional Types ve Mapped Types sayesinde tipler üzerinde koşul çalıştırabilir, koleksiyonları dönüştürebilir ve API sözleşmelerini otomatik üretebiliriz. Başka bir deyişle derleyiciye küçük ama son derece titiz bir program yazdırırız; üstelik bu program çalışma zamanında değil, kod daha çalışmadan önce görev yapar.
``
Tip seviyesinde programlama mantığı
Normal bir programda değerler girdi, fonksiyonlar ise dönüşüm aracıdır. Tip seviyesinde programlamada girdiler türlerdir ve sonuç yine bir türdür. Bunu matematiksel olarak $f(T) = U$ biçiminde düşünebiliriz. Conditional Type bir if-else, Mapped Type ise anahtarlar üzerinde çalışan bir for döngüsü gibi davranır.
| Çalışma zamanı yapısı | Tip sistemi karşılığı | Amaç |
|---|---|---|
if / else |
Conditional Type | Tür seçmek |
for...in |
Mapped Type | Özellikleri dönüştürmek |
| Geçici değişken | infer |
Tür parçasını çıkarmak |
| Fonksiyon zinciri | İç içe tipler | Dönüşümleri birleştirmek |
Bu benzetme birebir uygulama değildir; tipler JavaScript çıktısında silinir. Kazancımız çalışma zamanı performansı değil, güçlü statik doğrulamadır.
Conditional Types: Derleyicinin karar mekanizması
Temel sözdizimi şöyledir:
type IsString<T> = T extends string ? true : false;
type A = IsString<"merhaba">; // true
type B = IsString<42>; // false
Buradaki extends, sınıf kalıtımından çok “bu tipe atanabilir mi?” sorusunu sorar. Genel formülümüz $T \subseteq U$ ise birinci dal, değilse ikinci daldır.
infer anahtar sözcüğü ise eşleşen yapının içinden tür çıkarmamızı sağlar:
type FunctionResult<T> =
T extends (...args: any[]) => infer R ? R : never;
const loadUser = () => ({ id: 1, name: "Ada" });
type User = FunctionResult<typeof loadUser>;
// { id: number; name: string }
Bu tip, fonksiyonun dönüş değerini elle tekrar yazma ihtiyacını ortadan kaldırır. Fonksiyon değiştiğinde sözleşme de otomatik güncellenir.
Conditional Types birleşim tiplerine dağıtılabilir. T = A | B için dönüşüm kabaca $F(A \vert B) = F(A) \vert F(B)$ şeklinde gerçekleşir:
type OnlyStrings<T> = T extends string ? T : never;
type Result = OnlyStrings<string | number | boolean>;
// string
Dağılım istenmiyorsa iki tarafı tuple içine alabiliriz: [T] extends [U]. Bu küçük köşeli parantezler bazen saatlerce sürecek bir tip bulmacasını saniyeler içinde çözer.
Mapped Types: Özellikler üzerinde döngü
Mapped Types, keyof ile elde edilen anahtarları dolaşır:
type Nullable<T> = {
[K in keyof T]: T[K] | null;
};
type User = { id: number; name: string };
type NullableUser = Nullable<User>;
K, her turdaki özellik anahtarıdır; T[K] ise o özelliğin türüdür. readonly, opsiyonellik ve anahtar adları da değiştirilebilir.
| Operatör | Etki |
|---|---|
+? veya ? |
Özelliği opsiyonel yapar |
-? |
Opsiyonelliği kaldırır |
readonly |
Değiştirmeyi engeller |
-readonly |
Değiştirilebilir yapar |
as |
Anahtarı yeniden adlandırır veya eler |
Örneğin yalnızca fonksiyon özelliklerini seçebiliriz:
type MethodsOnly<T> = {
[K in keyof T as T[K] extends Function ? K : never]: T[K]
};
type Service = {
url: string;
start(): void;
stop(): void;
};
type ServiceMethods = MethodsOnly<Service>;
// { start(): void; stop(): void }
Burada Conditional Type filtreyi, Mapped Type döngüyü gerçekleştirir. never üreten anahtarlar sonuçtan çıkarılır.
Kırılmaz API sözleşmesi örneği
Bir modelden otomatik güncelleme girdisi üretelim; id değiştirilemesin, diğer alanlar opsiyonel olsun:
type UpdatePayload<T extends { id: unknown }> = {
[K in keyof T as K extends "id" ? never : K]?: T[K]
};
type Product = {
id: number;
title: string;
price: number;
active: boolean;
};
type ProductUpdate = UpdatePayload<Product>;
// { title?: string; price?: number; active?: boolean }
Model genişlediğinde güncelleme tipi de genişler; unutulmuş alanlar ve kopyala-yapıştır hataları azalır. Yine de aşırı karmaşık tipler derleyiciyi yavaşlatabilir ve hata mesajlarını okunmaz hâle getirebilir. İyi tip jimnastiğinin amacı en kısa akrobatik çözüm değil, değişikliklere dayanıklı ve ekipçe anlaşılabilir bir sözleşme kurmaktır.
Yorumlar