İçeriğe geç
academia.sh

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:

T extends U  ?  X:YT \text{ extends } U \;?\; X : Y

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; extends burada 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 never birlikte, 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.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat