Ders 15 / 23
Koşullu Tipler
Tip düzeyinde dallanma, birleşimler üzerinde dağılım kuralı ve dağılımın kapatılması, infer ile tip parçası çıkarma ve koşullu dönüş tiplerinin sınırı.
İçindekiler
Önceki derste T[A] extends number ? A : never biçiminde bir yazım geçti ve tip düzeyinde
bir dallanma yaptı. Koşullu tip (conditional type), tip sisteminin hesaplama
yeteneğinin merkezindeki yapıdır: bir tipin başka bir tipe atanabilir olup olmadığına göre
iki tipten birini seçer.
Yazımı üçlü işlecin tip düzeyindeki karşılığıdır:
Bu ders koşulun nasıl değerlendirildiğini, birleşimler üzerindeki özel davranışını,
infer ile parça çıkarmayı ve yapının sınırlarını ele alır.
Tip Düzeyinde Dallanma
type SayiMi<T> = T extends number ? "sayi" : "sayi degil"; const a: SayiMi<number> = "sayi"; const b: SayiMi<string> = "sayi degil"; console.log(a, b); const c: SayiMi<number> = "sayi degil";
p1.ts(7,7): error TS2322: Type '"sayi degil"' is not assignable to type '"sayi"'.
SayiMi<number> tipi "sayi", SayiMi<string> tipi "sayi degil" olarak çözülür.
Yedinci satırın tanısı, çözümün gerçekten yapıldığını gösterir: derleyici SayiMi<number>
yerine "sayi" yazarak hatayı bildirir.
extends sözcüğü buradaki anlamıyla kalıtım değil atanabilirlik sınamasıdır: “T,
U’nun beklendiği yere konabilir mi?” Yapısal tipleme dersinde kurulan kurallar burada
uygulanır.
Dağılım
Koşullu tipin tip parametresi bir birleşimse, koşul her üye için ayrı ayrı değerlendirilir ve sonuçların birleşimi alınır. Buna dağılım (distribution) denir:
type SayiMi<T> = T extends number ? "sayi" : "sayi degil"; type Dagilmayan<T> = [T] extends [number] ? "sayi" : "sayi degil"; declare const dagilan: SayiMi<number | string>; declare const kapali: Dagilmayan<number | string>; const x: null = dagilan; const y: null = kapali;
p2.ts(7,7): error TS2322: Type '"sayi degil" | "sayi"' is not assignable to type 'null'. Type '"sayi degil"' is not assignable to type 'null'. p2.ts(8,7): error TS2322: Type '"sayi degil"' is not assignable to type 'null'.
Tanılar iki tipi de açıkça yazıyor. SayiMi<number | string> tipi
"sayi degil" | "sayi" olmuş; koşul önce number, sonra string için
değerlendirilmiştir. Dagilmayan<number | string> ise tek bir sonuç vermiştir:
"sayi degil".
Dağılımı kapatan şey, tip parametresinin demet içine alınmasıdır. [T] extends [U]
yazımında sınanan artık T değil, tek elemanlı bir demettir; birleşim üyelerine ayrılmaz
ve koşul bir kez değerlendirilir.
İki davranıştan hangisinin isteneceği amaca bağlıdır. Bir birleşimin her üyesini
dönüştürmek isteniyorsa dağılım gerekir. “Bu tipin tümü şu koşulu sağlıyor mu?”
sorusuna yanıt aranıyorsa kapatılmalıdır — aksi hâlde number | string birleşimi için
“kısmen sayı” gibi anlamsız bir sonuç çıkar.
Dağılım yalnızca çıplak tip parametrelerinde geçerlidir: koşulun sol tarafı doğrudan
T olmalıdır. T[], [T] ya da { deger: T } yazımlarında dağılım olmaz.
Süzme: Cikar
Dağılımın en yaygın kullanımı, bir birleşimden üye elemektir. Elenecek üye için never
üretilir; never bir birleşimde yer kaplamaz:
type Cikar<T, U> = T extends U ? never : T; type Birim = "C" | "Pa" | "%"; type OlculebilirBirim = Cikar<Birim, "%">; const birimler: OlculebilirBirim[] = ["C", "Pa"]; console.log(birimler.join(","));
Çıktı C,Padır. Dosyanın sonuna, bir boş satırdan sonra
const yanlis: OlculebilirBirim = "%"; satırı eklenirse:
p5.ts(9,7): error TS2322: Type '"%"' is not assignable to type 'OlculebilirBirim'.
Hesabın adımları şöyledir: koşul üç üye için ayrı ayrı değerlendirilir; "C" ve "Pa"
için "%" tipine atanamadıklarından kendileri, "%" için ise never üretilir. Sonuç
"C" | "Pa" | never yani "C" | "Pa"dır.
Bu dönüşümün standart kitaplıktaki karşılığı Excludetur ve Yardımcı Tipler dersinde ele
alınacaktır.
infer ile Parça Çıkarma
Koşulun sol tarafında infer sözcüğüyle bir tip değişkeni bildirilebilir. Derleyici,
eşleştirme sırasında o değişkene karşılık gelen tipi bulur:
interface Olcum { kimlik: string; deger: number; } type ElemanTipi<T> = T extends readonly (infer E)[] ? E : never; declare const eleman: ElemanTipi<Olcum[]>; declare const dizgiEleman: ElemanTipi<readonly string[]>; declare const dizisiz: ElemanTipi<number>; const a: null = eleman; const b: null = dizgiEleman; const c: null = dizisiz;
p3.ts(12,7): error TS2322: Type 'Olcum' is not assignable to type 'null'. p3.ts(13,7): error TS2322: Type 'string' is not assignable to type 'null'.
ElemanTipi<Olcum[]> tipi Olcum, ElemanTipi<readonly string[]> tipi string olmuştur.
On dördüncü satırın tanı vermemesi de bilgi taşır: ElemanTipi<number> tipi neverdir ve
never her tipe — null dahil — atanabilir.
Aynı teknik, kursun sonuç tipinden yük tipini çıkarmak için kullanılır:
interface Olcum { kimlik: string; deger: number; } type Sonuc<D, H = string> = | { durum: "basarili"; deger: D } | { durum: "hatali"; hata: H }; type BasariTipi<S> = S extends { durum: "basarili"; deger: infer D } ? D : never; type HataTipi<S> = S extends { durum: "hatali"; hata: infer H } ? H : never; declare const basari: BasariTipi<Sonuc<Olcum>>; declare const hata: HataTipi<Sonuc<Olcum, number>>; const a: null = basari; const b: null = hata;
p4.ts(16,7): error TS2322: Type 'Olcum' is not assignable to type 'null'. p4.ts(17,7): error TS2322: Type 'number' is not assignable to type 'null'.
Dağılım burada da iş görüyor: Sonuc<Olcum> iki üyeli bir birleşimdir; BasariTipi
hesabında başarı üyesi Olcum, hata üyesi never verir ve birleşim Olcuma indirgenir.
Bu kalıbın değeri, bir tipin tanımını tekrarlamadan parçasına erişebilmektir. Sonuc
tipinin yapısı değişirse BasariTipi da değişir; ikisi arasında elle tutulan bir bağ
kalmaz.
Koşullu Dönüş Tipinin Sınırı
Koşullu tipler fonksiyon imzalarında kullanılabilir, ancak gövde içinde çözülmezler:
type Bicim<T> = T extends number ? string : number; function bicimle<T extends number | boolean>(deger: T): Bicim<T> { if (typeof deger === "number") { return deger.toFixed(1); } return 0; } console.log(bicimle(21.4), bicimle(true));
p7.ts(5,5): error TS2322: Type 'string' is not assignable to type 'Bicim<T>'. p7.ts(7,3): error TS2322: Type 'number' is not assignable to type 'Bicim<T>'.
Gövde içinde T henüz bilinmediği için Bicim<T> çözülemez; derleyici, typeof deger
sınamasının Tyi de daralttığını çıkaramaz. Tanı iki dalda birden gelir.
Bu, tip sisteminin bilinen bir sınırıdır. İki çözüm vardır: gövdede tek bir tip iddiası kullanmak — sınırı görünür kılan, imzada verilen sözü gövdenin taşıdığını kabul eden bir borç — ya da koşullu tip yerine aşırı yükleme yazmak. İkincisi, Fonksiyon Tipleri dersinde kurulan yapıdır ve çağıranlar için aynı sonucu verir.
Genel ilke: koşullu tipler tip hesaplamak için güçlüdür, gerçekleştirimi yönlendirmek için değil.
Özet
- Koşullu tip, atanabilirlik sınamasına göre iki tipten birini seçer;
extendsburada kalıtım değil atanabilirlik anlamındadır. - Çıplak bir tip parametresi birleşimse koşul her üye için ayrı değerlendirilir ve
sonuçların birleşimi alınır;
[T] extends [U]yazımı bu dağılımı kapatır. - Dağılım ve
neverbirlikte, bir birleşimden üye eleyen süzgeçler kurar. infer, koşulun sol tarafında bir tip değişkeni bildirir ve eşleştirme sırasında ona karşılık gelen tipi çıkarır; bir tipin parçasına tanımı tekrarlamadan erişmeyi sağlar.- Koşullu dönüş tipleri fonksiyon gövdesi içinde çözülmez; bu durumda tip iddiası ya da aşırı yükleme gerekir.
Sonraki Adım
Anahtar yeniden eşlemesi dersinde `oku${Capitalize<string & A>}` biçiminde bir yazım
kullanıldı ve alan adı hesaplandı. Dizgilerin tip düzeyinde birleştirilmesi ve
ayrıştırılması ayrı bir araç kümesidir. Sonraki ders şablon dizgi tiplerini ve bunların
infer ile birlikte dizgi biçimlerini çözümlemekte nasıl kullanıldığını ele alacak.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.