Koşullu tip, JavaScript'teki üçlü operatör gibi okunan bir testle iki tip arasında seçim yapar: T extends U ? X : Y. T, U tipine atanabiliyorsa sonuç X, aksi halde Y olur.
Buradaki extends "atanabilir" anlamına gelir; bu, bir değişkene değer atadığınızda derleyicinin kullandığı ilişkinin aynısıdır. Koşullu tipler yalnızca derleme zamanında vardır; JavaScript çıktısından silinirler.
Sözdizimi
type Result = CheckedType extends TestType ? TrueType : FalseType;
Koşullu tipler, kontrol edilen tipin daha sonra somut bir tip alan bir tip parametresi olduğu generics ile işe yarar hale gelir. true dalında TypeScript kontrol edilen tipin teste uyduğunu bilir; bu yüzden düz T tipinde message özelliği olmasa bile yukarıdaki T["message"] ifadesine izin verilir.
Zincirlenmiş üçlü operatörler gibi iç içe de yazılabilirler:
Fonksiyon gövdesindeki as TypeName<T> isteğe bağlı değildir; bir sonraki bölüm bunu gösteriyor.
Koşullu Dönüş Tipleri Bir Assertion Gerektirir
Dönüş tipi kendi tip parametresi üzerinde bir koşullu tip olan bir fonksiyon, dallardan hiçbirini doğrudan döndüremez. TypeScript gövde içinde T tipini daraltmaz, bu yüzden hangi dalın geçerli olduğunu söyleyemez:
index.ts(5,34): error TS2322: Type 'number' is not assignable to type 'Flip<T>'.
index.ts(5,45): error TS2322: Type 'string' is not assignable to type 'Flip<T>'.
İki yaygın çözüm var: her girdi çıktı çiftini belirten ve çağıranları kesin olarak kontrol eden overload'lar ya da implementasyonda bir assertion.
Overload'lar fonksiyon overload sayfasında anlatılıyor. Assertion ile derleyici size güvenir: gövdedeki yanlış bir dal yakalanmaz.
Dağılan Koşullu Tipler
Kontrol edilen tip çıplak bir tip parametresi olduğunda ve bir union aldığında, koşul her üye için bir kez çalışır ve sonuçlar yeni bir union'da birleştirilir:
Exclude ve Extract tiplerini çalıştıran şey bu dağılımdır. Exclude<T, U>, T extends U ? never : T olarak tanımlanır: U ile eşleşen her üye never olur ve never bir union'dan kaybolur. Bu yüzden Exclude<"a" | "b" | "c", "a">, "b" | "c" olur.
Aynı kuraldan iki sürpriz çıkar. boolean, true | false union'ıdır; bu yüzden ToArray<boolean>, boolean[] değil false[] | true[] olur. Ve never boş union'dır; dolayısıyla never verilen dağılan bir koşullu tip hiçbir şeyi test etmeden never döndürür:
type IsNever<T> = T extends never ? true : false;
type X = IsNever<never>; // never, not true
type IsNeverFixed<T> = [T] extends [never] ? true : false;
type Y = IsNeverFixed<never>; // true
infer ile Tip Çıkarmak
infer, extends ifadesinin içinde yeni bir tip değişkeni bildirir. Eşleşme başarılı olursa TypeScript bu değişkeni kontrol edilen tipten doldurur ve onu true dalında kullanabilirsiniz:
T extends Promise<infer V> ? V : T ifadesini şöyle okuyun: "T bir şeyin promise'i ise, o şeye V de ve onu döndür; değilse T tipini olduğu gibi döndür". infer yalnızca bir koşullu tipin extends ifadesinde kullanılabilir.
Bir infer değişkeni extends ile kendi kısıtlamasını taşıyabilir. O zaman eşleşme yalnızca çıkarılan tip uyarsa başarılı olur:
type FirstString<T> = T extends [infer S extends string, ...unknown[]] ? S : never;
type A = FirstString<["a", 1]>; // "a"
type B = FirstString<[1, "a"]>; // never: the first element is not a string
ReturnType'ı Kendiniz Yazmak
Yerleşik ReturnType, infer içeren tek satırlık bir koşullu tiptir. Onu kendiniz yazmak, iki fikri de oturtan klasik alıştırmadır:
typeof makeUser fonksiyon değerini tipine çevirir, ardından koşullu tip onu "herhangi bir fonksiyon" ile eşleştirir ve dönüş tipini R olarak yakalar. Standart kütüphanedeki sürüm iki ayrıntıda farklıdır: parametresi fonksiyon tipleriyle kısıtlanmıştır (T extends (...args: any) => any), bu yüzden ReturnType<string> never değil bir derleme hatası olur; false dalı ise any olur. ReturnType sayfası Parameters, InstanceType ve Awaited tiplerini de anlatıyor.
Özyinelemeli Koşullu Tipler
Bir koşullu tip kendisine başvurabilir; bu da her derinlikteki iç içe yapıyı açmasını sağlar:
type Flatten<T> = T extends readonly (infer U)[] ? Flatten<U> : T;
type A = Flatten<number[][][]>; // number
type B = Flatten<string>; // string
Yerleşik Awaited<T> bu şekilde çalışır ve Promise<Promise<T>> tipini T tipine kadar açar. Pratikte özyinelemeyi sığ tutun: çok derin ya da sınırsız özyineleme derleyicinin error TS2589: Type instantiation is excessively deep and possibly infinite ile vazgeçmesine neden olur.
Hızlı Başvuru
| Kalıp | Anlamı |
|---|---|
T extends U ? X : Y | T, U tipine atanabiliyorsa X, değilse Y |
T extends U ? never : T | U ile eşleşen üyeleri çıkarır (bu Exclude tipidir) |
T extends U ? T : never | U ile eşleşen üyeleri tutar (bu Extract tipidir) |
[T] extends [U] ? X : Y | aynı test, bir union üzerinde dağılmadan |
T extends (infer E)[] ? E : T | bir dizinin eleman tipi |
T extends Promise<infer V> ? V : T | bir promise'in değer tipi |
T extends (...args: any[]) => infer R ? R : never | bir fonksiyonun dönüş tipi |
T extends [infer H, ...infer Rest] ? ... | bir tuple'ın ilk elemanı ve geri kalanı |
Sıkça Sorulan Sorular
TypeScript'te koşullu tip nedir?
T extends U ? X : Y biçimindeki bir tiptir. T, U tipine atanabiliyorsa sonuç X, değilse Y olur. Tipler için derleme zamanında değerlendirilen bir if/else'tir; üretilen JavaScript'te ondan hiçbir şey kalmaz.
TypeScript'te infer anahtar kelimesi ne işe yarar?
infer, bir koşullu tipin extends ifadesi içinde bir tip değişkeni bildirir ve TypeScript'in onu eşleşen tipten doldurmasını sağlar. T extends Promise<infer V> ? V : T, bir promise tipinden çözümlenen değerin tipini çıkarır. Yalnızca bir koşullu tipin extends ifadesinde kullanılabilir.
Dağılan (distributive) koşullu tip nedir?
Kontrol edilen tip çıplak bir tip parametresi olduğunda ve ona bir union geçirdiğinizde, koşul her üyeye ayrı ayrı uygulanır ve sonuçlar birleştirilir. type ToArray<T> = T extends unknown ? T[] : never ile ToArray<string | number>, string[] | number[] olur. Bunu kapatmak için iki tarafı da köşeli parantez içine alın: [T] extends [unknown].
TypeScript'te bir fonksiyonun dönüş tipini nasıl alırım?
Yerleşik ReturnType<typeof fn> tipini kullanın. infer içeren bir koşullu tiptir: T extends (...args: any) => infer R ? R : any. Async fonksiyonlarda promise yerine çözümlenen değeri almak için onu Awaited<...> ile sarın.
IsNever<never> neden true yerine never döndürür?
never boş union'dır ve dağılan bir koşullu tip bir union'ın üyeleri üzerinde eşleme yapar. Hiç üye yoksa eşlenecek bir şey de yoktur ve sonuç never olur. never tipinin kendisini test etmek için [T] extends [never] ? true : false yazın.