İçeriğe geç
academia.sh

Ders 14 / 23

Eşlenmiş Tipler

Var olan bir tipten yeni tip üretme; alan değiştiricilerinin eklenmesi ve kaldırılması, anahtar yeniden eşlemesi, alan süzme ve eşlemenin dizi ile demetler üzerindeki davranışı.

İçindekiler

Ölçüm kaydının değişik biçimleri şimdiye kadar elle yazıldı: arşivlenmiş kayıtlar için salt okunur bir sürüm, güncelleme isteği için alanları seçmeli bir sürüm, listeleme için yalnızca birkaç alanı taşıyan bir özet.

Her biri asıl tipin bir dönüşümüdür. Elle yazıldıklarında asıl tip değiştiğinde birlikte güncellenmeleri gerekir ve bu, unutulmaya elverişli bir bakım borcudur. Eşlenmiş tip (mapped type), dönüşümü tip düzeyinde hesaplar: alan listesi tek yerde durur, türevler kendiliğinden güncellenir.

Temel Yazım

Eşlenmiş tip, bir anahtar kümesi üzerinde dolaşan bir döngüye benzer:

interface Olcum {
  kimlik: string;
  deger: number;
  birim: "C" | "Pa" | "%";
}

type SaltOkunur<T> = { readonly [A in keyof T]: T[A] };
type Yamalanabilir<T> = { [A in keyof T]?: T[A] };

const arsivKayit: SaltOkunur<Olcum> = {
  kimlik: "s-01",
  deger: 21.4,
  birim: "C",
};
console.log(arsivKayit.deger);

const yama: Yamalanabilir<Olcum> = { deger: 22.1 };
console.log(yama.deger, yama.kimlik);

Çıktı:

21.4
22.1 undefined

Dosyanın sonuna, bir boş satırdan sonra arsivKayit.deger = 22.1; satırı eklenirse:

o2.ts(20,12): error TS2540: Cannot assign to 'deger' because it is a read-only property.

Yazımın parçaları şunlardır. [A in keyof T] bölümü, keyof T birleşiminin her üyesini sırayla A adına bağlar — Programlama Temelleri kursundaki for döngüsünün tip düzeyindeki karşılığı. İki nokta üstündeki T[A], o alanın asıl tipidir. Başa yazılan readonly ve ada eklenen ?, her alana uygulanan değiştiricilerdir.

Yamalanabilir<Olcum> tipinin bütün alanları seçmeli olduğu için { deger: 22.1 } sabiti kabul edilir; yama.kimlik erişiminin tipi string | undefineddır ve çıktıda undefined görünür.

Bu iki dönüşümün karşılığı standart kitaplıkta zaten bulunur (Readonly ve Partial); Yardımcı Tipler dersinde ele alınacaklardır. Burada elle yazılmalarının nedeni, mekanizmanın görünür olmasıdır.

Değiştirici Kaldırma

Değiştiriciler eklenebildiği gibi kaldırılabilir de. Kaldırma, değiştiricinin önüne eksi işareti konarak yazılır:

interface Olcum {
  readonly kimlik: string;
  readonly deger?: number;
}

type Cozulmus<T> = { -readonly [A in keyof T]-?: T[A] };

const tam: Cozulmus<Olcum> = { kimlik: "s-01", deger: 21.4 };
tam.deger = 22.1;
console.log(tam.deger.toFixed(1));

Çıktı 22.1dir. Dosyanın sonuna, bir boş satırdan sonra const eksik: Cozulmus<Olcum> = { kimlik: "s-02" }; satırı eklenirse:

o4.ts(12,7): error TS2741: Property 'deger' is missing in type '{ kimlik: string; }' but required in type 'Cozulmus<Olcum>'.

Olcum tipinde kimlik salt okunur, deger hem salt okunur hem seçmeliydi. Cozulmus<Olcum> tipinde ikisi de yazılabilir ve zorunludur. tam.deger.toFixed(1) çağrısının daraltma gerektirmemesi bunun sonucudur: alan artık numberdır, number | undefined değil.

Bu dönüşümün kullanım yeri, kısmi bir kaydın tamamlanmış hâlini temsil etmektir. Varsayılan değerler uygulandıktan sonra elde edilen kayıt için ayrı bir tip yazmak yerine, asıl tipten hesaplanır.

Anahtar Yeniden Eşlemesi

as yazımı, üretilen alanın adını değiştirmeyi sağlar:

interface Olcum {
  kimlik: string;
  deger: number;
  birim: "C" | "Pa" | "%";
}

type Okuyucular<T> = {
  [A in keyof T as `oku${Capitalize<string & A>}`]: () => T[A];
};

const okuyucu: Okuyucular<Olcum> = {
  okuKimlik: () => "s-01",
  okuDeger: () => 21.4,
  okuBirim: () => "C",
};

console.log(okuyucu.okuDeger().toFixed(1), okuyucu.okuKimlik());

Çıktı 21.4 s-01dir.

Üretilen tip, her alan için bir okuyucu fonksiyon bildirir ve dönüş tipini asıl alandan alır: okuDeger çağrısı number, okuBirim çağrısı "C" | "Pa" | "%" verir. Ad üretiminde kullanılan şablon dizgi yazımı ile Capitalize dönüşümü sonraki dersin konusudur; burada yalnızca adın hesaplanabildiği görülmelidir.

Yeniden eşlemenin ikinci ve daha güçlü kullanımı, bir alanı elemektir. Üretilen ad never olduğunda o alan çıktıya girmez:

interface Olcum {
  kimlik: string;
  deger: number;
  birim: "C" | "Pa" | "%";
  zaman: number;
}

type SayisalAlanlar<T> = {
  [A in keyof T as T[A] extends number ? A : never]: T[A];
};

const sayisal: SayisalAlanlar<Olcum> = { deger: 21.4, zaman: 1706000000000 };
console.log(sayisal.deger + sayisal.zaman);

Çıktı 1706000000021.4tür. Dosyanın sonuna, bir boş satırdan sonra const yanlis: SayisalAlanlar<Olcum> = { deger: 21.4, zaman: 1, kimlik: "s-01" }; satırı eklenirse:

o7.ts(15,64): error TS2353: Object literal may only specify known properties, and 'kimlik' does not exist in type 'SayisalAlanlar<Olcum>'.

Sonuç tipi yalnızca deger ve zaman alanlarını taşır; kimlik ve birim elenmiştir. T[A] extends number ? A : never yazımı bir koşullu tiptir ve sonraki derslerden birinin konusudur; burada işlevi, alan tipine bakarak adı tutmak veya never üretip elemektir.

Bu tip, ölçüm kaydının sayısal özetini çıkaran kodun imzasında kullanılabilir. Kayda yeni bir sayısal alan eklendiğinde özet tipi kendiliğinden büyür; dizgi bir alan eklendiğinde etkilenmez.

Diziler ve Demetler Üzerinde Eşleme

keyof T üzerinde dolaşan bir eşleme diziye uygulandığında beklenmedik bir şey olmaz: sonuç yine bir dizidir.

type SaltOkunur<T> = { readonly [A in keyof T]: T[A] };

const degerler: SaltOkunur<number[]> = [21.4, 22.1];
const aralik: SaltOkunur<[alt: number, ust: number]> = [-40, 85];

console.log(degerler.length, degerler[0], aralik[1]);
console.log(degerler.map((d) => d * 2));

Çıktı:

2 21.4 85
[ 42.8, 44.2 ]

SaltOkunur<number[]> tipi readonly number[], SaltOkunur<[number, number]> tipi salt okunur bir demettir. Dizi yöntemleri (map, length, indeksli erişim) kullanılabilir durumda kalmış, değiştirme yöntemleri kalkmıştır:

type SaltOkunur<T> = { readonly [A in keyof T]: T[A] };

const degerler: SaltOkunur<number[]> = [21.4, 22.1];
degerler.push(23.0);
u4.ts(4,10): error TS2339: Property 'push' does not exist on type 'readonly number[]'.

Bu davranış, eşlemenin kaynağı doğrudan keyof T olduğunda geçerlidir. Derleyici bu biçimi tanır ve dizi ile demetleri kendi türlerinde tutar. Kaynak başka bir ifadeyle yazıldığında tanıma bozulur:

type EslemA<T> = { readonly [A in keyof T]: T[A] };
type EslemB<T> = { readonly [A in keyof T & string]: T[A] };

declare const a: EslemA<number[]>;
declare const b: EslemB<number[]>;

const x: null = a;
const y: null = b;
u11.ts(7,7): error TS2322: Type 'readonly number[]' is not assignable to type 'null'.
u11.ts(8,7): error TS2322: Type 'EslemB<number[]>' is not assignable to type 'null'.

Tanıların farkı belirleyicidir. EslemA<number[]> tipi readonly number[] olarak çözülmüştür — derleyici sonucu bir dizi olarak yazıyor. EslemB<number[]> ise çözülmemiş bir eşleme olarak kalmıştır; dizi yapısı kaybolmuş, yerine dizinin özellik adlarını taşıyan sıradan bir nesne tipi geçmiştir.

Pratik kural: bir tipin yapısını korumak isteniyorsa eşleme kaynağı keyof T olarak yazılır. Anahtar süzmek gerekiyorsa süzme, as yeniden eşlemesiyle yapılır — kaynak keyof T kalır, ad hesabı sağ tarafta olur.

Herhangi Bir Birleşim Üzerinde Eşleme

Eşleme kaynağının keyof T olması zorunlu değildir; herhangi bir dizgi birleşimi kullanılabilir:

type Birim = "C" | "Pa" | "%";

type Araliklar = {
  [B in Birim]: [alt: number, ust: number];
};

const araliklar: Araliklar = {
  C: [-40, 85],
  Pa: [0, 200000],
  "%": [0, 100],
};

console.log(araliklar.C[1], araliklar["%"][1]);

Çıktı 85 100dür. Dosyanın sonuna, bir boş satırdan sonra const eksik: Araliklar = { C: [-40, 85], Pa: [0, 200000] }; satırı eklenirse:

o9.ts(15,7): error TS2741: Property '"%"' is missing in type '{ C: [number, number]; Pa: [number, number]; }' but required in type 'Araliklar'.

Bu, Arayüzler dersinde eleştirilen indeks imzasının doğru alternatifidir. İndeks imzası “her dizgi anahtar geçerlidir” der ve olmayan anahtara erişimi hata saymazdı. Eşlenmiş tip ise anahtar kümesini tam olarak belirler: eksik bir anahtar tanı verir, fazladan bir anahtar da öyle.

Değer kümesi Birim birleşimine yeni bir birim eklendiğinde, aralık tablosunu güncellemek zorunlu hâle gelir. Ayırt edici birleşimde never ile kurulan tükenmişlik denetiminin, veri tabloları için karşılığıdır.

Özet

  • Eşlenmiş tip, bir anahtar kümesi üzerinde dolaşarak yeni bir nesne tipi üretir; türev tipler asıl tiple birlikte güncellenir.
  • readonly ve ? değiştiricileri eşleme sırasında eklenebilir; -readonly ve -? yazımlarıyla kaldırılabilir.
  • as yazımı üretilen alanın adını hesaplar; ad never olduğunda alan çıktıdan elenir.
  • Eşleme kaynağı keyof T olmak zorunda değildir; herhangi bir dizgi birleşimi üzerinde eşleme yapılabilir.
  • Eşleme kaynağı doğrudan keyof T olduğunda dizi ve demetler kendi türlerinde kalır; kaynak başka bir ifadeyle yazıldığında bu yapı kaybolur.
  • Sabit anahtar kümesi için eşlenmiş tip, indeks imzasından güvenlidir: eksik ve fazla anahtarların ikisi de bildirilir.

Sonraki Adım

Önceki iki örnekte T[A] extends number ? A : never biçiminde bir yazım geçti ve tip düzeyinde bir dallanma yaptı. Bu, tip sisteminin hesaplama yeteneğinin merkezindeki yapıdır. Sonraki ders koşullu tipleri, dağılım kurallarını ve bir tipin içinden parça çıkaran infer yazımı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