Ders 13 / 23
Tip Daraltma
Denetim akışı çözümlemesiyle tip daraltma; typeof, in ve instanceof koruyucuları, doğruluk daraltmasının tuzağı, tip yüklemleri, doğrulama fonksiyonları ve daraltmanın kaybolduğu durumlar.
İçindekiler
Önceki derslerde birkaç kez bir birleşim tipi üzerinde sınama yapıldı ve derleyici tipi tek bir üyeye indirdi. Bu indirgemenin kuralları henüz sıralanmadı.
Daraltma (narrowing), derleyicinin bir değerin tipini denetim akışındaki sınamalara bakarak küçültmesidir. Arkasındaki mekanizmaya denetim akışı çözümlemesi (control flow analysis) denir: derleyici, programın her noktasında hangi koşulların kanıtlanmış olduğunu izler. Bu ders hangi sınamaların kanıt sayıldığını, kendi doğrulamanızın derleyiciye nasıl bildirileceğini ve kanıtın nerede kaybolduğunu ele alır.
Yerleşik Koruyucular
Tip koruyucusu (type guard), bir değerin tipini daraltan sınamadır. En yaygını
typeof işlecidir:
function bicimle(deger: string | number | null): string { if (deger === null) { return "-"; } if (typeof deger === "number") { return deger.toFixed(1); } return deger.trim(); } console.log(bicimle(21.4), bicimle(" s-01 "), bicimle(null));
Çıktı 21.4 s-01 -tir.
Üç dal, üç daraltma adımıdır. deger === null sınaması eşitlikle daraltır; ilk return
sonrası kalan tip string | numberdır. typeof deger === "number" sınamasından sonra
gövdede tip number olur ve toFixed çağrılabilir. Son satıra ulaşıldığında kalan tek
olasılık stringtir; trim çağrısı için ek bir sınama gerekmez.
Son adım dikkat ister: derleyici erken dönüşleri izler. return deyimiyle biten bir
dal, sonraki kodda o olasılığı eler. Programlama Temelleri kursunda tanıtılan koruma
cümlesi kalıbı, tip düzeyinde de kazanç sağlar.
Nesne tipleri için iki koruyucu daha vardır. in işleci alan varlığına bakar:
type SayisalOlcum = { kimlik: string; deger: number }; type MetinselOlcum = { kimlik: string; metin: string }; function yaz(kayit: SayisalOlcum | MetinselOlcum): string { if ("deger" in kayit) { return kayit.deger.toFixed(1); } return kayit.metin.trim(); } console.log(yaz({ kimlik: "s-01", deger: 21.4 })); console.log(yaz({ kimlik: "s-02", metin: " bozuk " }));
Çıktı:
21.4 bozuk
instanceof işleci ise sınıf örneklerini ayırır:
class DosyaKaynagi { constructor(readonly yol: string) {} } class AgKaynagi { constructor(readonly adres: string) {} } function tanim(kaynak: DosyaKaynagi | AgKaynagi): string { if (kaynak instanceof DosyaKaynagi) { return `dosya: ${kaynak.yol}`; } return `ag: ${kaynak.adres}`; } console.log(tanim(new DosyaKaynagi("/veri/olcum.jsonl"))); console.log(tanim(new AgKaynagi("10.0.0.7:9000")));
Çıktı:
dosya: /veri/olcum.jsonl ag: 10.0.0.7:9000
Bu üçünün ortak özelliği, hepsinin çalışma zamanında gerçekten çalışan sınamalar olmasıdır. Daraltma, tip sisteminin bir varsayımı değil; koddaki bir sınamanın tip düzeyine yansımasıdır.
Doğruluk Daraltmasının Tuzağı
Bir değerin doğruluk değerine bakmak da daraltır, ancak beklenenden fazlasını eler:
function etiket(deger: number | undefined): string { if (!deger) { return "olcum yok"; } return deger.toFixed(1); } console.log(etiket(21.4)); console.log(etiket(0)); console.log(etiket(undefined));
Çıktı:
21.4 olcum yok olcum yok
Bu dosya tip denetiminden geçer, ama ikinci çıktı yanlıştır: 0 geçerli bir ölçümdür.
JavaScript Temelleri kursunda kurulan yanlış sayılan değerler listesi burada devreye
girer — 0, "" ve NaN da yanlış sayılır ve !deger sınaması onları da eler.
Tip sistemi bu hatayı yakalamaz, çünkü daraltma doğru yapılmıştır: !deger bloğundan
sonra tip gerçekten numberdır. Hata tipte değil, sınamanın seçiminde.
Kural: undefined ve null için eşitlik sınaması yazılır. if (deger === undefined)
ya da if (deger == null) — ikincisi hem null hem undefined durumunu eler ve bu, iki
eşittirin savunulabilir tek kullanımıdır.
Tip Yüklemleri
Doğrulama ayrı bir fonksiyona taşındığında daraltma kaybolur: derleyici, boolean dönen
bir fonksiyonun neyi kanıtladığını bilmez. Tip yüklemi (type predicate), bu bilgiyi
imzada taşır:
interface Olcum { kimlik: string; deger: number; birim: "C" | "Pa" | "%"; } function olcumMu(deger: unknown): deger is Olcum { if (typeof deger !== "object" || deger === null) { return false; } const aday = deger as Record<string, unknown>; return ( typeof aday.kimlik === "string" && typeof aday.deger === "number" && (aday.birim === "C" || aday.birim === "Pa" || aday.birim === "%") ); } function oku(ham: string): string { const cozulen: unknown = JSON.parse(ham); if (!olcumMu(cozulen)) { return "gecersiz kayit"; } return `${cozulen.kimlik}=${cozulen.deger}${cozulen.birim}`; } console.log(oku('{"kimlik":"s-01","deger":21.4,"birim":"C"}')); console.log(oku('{"kimlik":"s-02","deger":"22.1","birim":"C"}')); console.log(oku('{"kimlik":"s-03","deger":22.1,"birim":"K"}'));
Çıktı:
s-01=21.4C gecersiz kayit gecersiz kayit
Dönüş tipi boolean değil deger is Olcum yazılmıştır. Bu, derleyiciye şunu söyler:
“bu fonksiyon true döndürdüğünde, argümanının Olcum olduğunu kabul et.” Sonuç, oku
içinde cozulen değerinin unknown olmaktan çıkıp Olcum olmasıdır.
Bu, kursun eksenindeki tek noktada toplanır: doğrulama çalışma zamanında yapılır, tip yüklemi bunun kanıt olarak kaydedilmesini sağlar. Model artık dış veriden güvenle kurulabilir.
Yüklemin bir bedeli vardır. Derleyici, gövdenin gerçekten iddia edileni doğruladığını sınamaz:
interface Olcum { kimlik: string; deger: number; } function olcumMu(deger: unknown): deger is Olcum { return typeof deger === "object"; } const aday: unknown = null; if (olcumMu(aday)) { console.log(aday.deger.toFixed(1)); }
Dosya tip denetiminden geçer; çalıştırıldığında:
TypeError: Cannot read properties of null (reading 'deger')
Gövde typeof null === "object" sonucunu true verir ve yanlış bir kanıt üretir. Tip
yüklemi, as gibi bir borçtur: doğruluğundan yazan sorumludur. Bu nedenle yüklem
gövdeleri kısa tutulur ve sınanır.
Doğrulama Fonksiyonları
Yüklemin ikinci biçimi, sınamayı geçemeyen değerde hata atar:
interface Olcum { kimlik: string; deger: number; } function olcumOldugunuDogrula(deger: unknown): asserts deger is Olcum { if ( typeof deger !== "object" || deger === null || typeof (deger as Record<string, unknown>).kimlik !== "string" || typeof (deger as Record<string, unknown>).deger !== "number" ) { throw new TypeError("olcum kaydi degil"); } } const cozulen: unknown = JSON.parse('{"kimlik":"s-01","deger":21.4}'); olcumOldugunuDogrula(cozulen); console.log(cozulen.kimlik, cozulen.deger.toFixed(1));
Çıktı s-01 21.4tür.
asserts deger is Olcum bildirimi, çağrının normal döndüğü durumda tipin daralmasını
sağlar. Bir if bloğuna gerek kalmaz; çağrıdan sonraki tüm kodda cozulen bir Olcumdur.
İki biçim arasındaki seçim, geçersiz verinin beklenen bir durum olup olmadığına bağlıdır. Programlama Temelleri kursundaki Tip Dönüşümü dersinde aynı ayrım yapılmıştı: kullanıcı girdisinde geçersizlik olağandır ve yüklemle ele alınır; programın kendi ürettiği verinin geçersizliği istisnaidir ve doğrulama fonksiyonu uygundur.
Bir kısıt vardır: doğrulama fonksiyonunun çağrıldığı ad, açıkça tiplenmiş bir bildirim
olmalıdır. Fonksiyon önce tipsiz bir sabite atanır ve o sabit üzerinden çağrılırsa
derleyici TS2775 tanısını verir: doğrulama çağrılarında hedefteki her adın açık tip
açıklaması taşıması gerekir.
Daraltmanın Kaybolduğu Yer
Denetim akışı çözümlemesi, değerin sınamadan sonra değişmediğini varsayabildiği sürece çalışır. Kapanış içinde bu varsayım geçersizdir:
interface Olcum { kimlik: string; son?: number; } function yaz(kayit: Olcum): void { if (kayit.son !== undefined) { console.log(kayit.son.toFixed(1)); [1, 2].forEach(() => { console.log(kayit.son.toFixed(1)); }); } } yaz({ kimlik: "s-01", son: 21.4 });
n6.ts(10,19): error TS18048: 'kayit.son' is possibly 'undefined'.
Sekizinci satır geçerlidir, onuncu satır değildir. Fark, geri çağrının ne zaman
çalışacağının bilinmemesidir: forEach senkron çalışır ama derleyici bunu bilemez, ve
arada kayit.son alanı silinmiş olabilir. Aynı sorun zamanlayıcılarda ve eşzamansız
kodda daha da belirgindir.
Çözüm, daraltılmış değeri yerel bir sabite almaktır:
interface Olcum { kimlik: string; son?: number; } function yaz(kayit: Olcum): void { const son = kayit.son; if (son !== undefined) { console.log(son.toFixed(1)); [1, 2].forEach(() => { console.log(son.toFixed(1)); }); } } yaz({ kimlik: "s-01", son: 21.4 });
const ile bağlanan bir ad değişemez; derleyici daraltmayı kapanış içine taşıyabilir.
Çıktı üç satır 21.4tür.
Özet
- Daraltma, denetim akışındaki sınamaların tip düzeyine yansımasıdır;
typeof,in,instanceofve eşitlik sınamaları yerleşik koruyuculardır. - Doğruluk sınaması
0ve""gibi geçerli değerleri de eler;nullveundefinediçin eşitlik sınaması yazılır. - Tip yüklemi (
deger is T), ayrı bir fonksiyona taşınmış doğrulamanın sonucunu derleyiciye bildirir; gövdenin doğruluğu sınanmaz, yüklem bir borçtur. - Doğrulama fonksiyonu (
asserts deger is T), geçersiz değerde hata atar ve normal döndüğünde tipi daraltır. - Daraltma kapanış içine taşınmaz; daraltılan değer yerel bir sabite alınarak bu sınır aşılır.
Sonraki Adım
Ölçüm kaydının değişik biçimleri şimdiye kadar elle yazıldı: salt okunur sürüm, alanları seçmeli olan güncelleme sürümü, yalnızca birkaç alanı taşıyan özet sürüm. Her biri asıl tipin bir dönüşümüdür ve tip değiştiğinde birlikte güncellenmeleri gerekir. Sonraki ders bu dönüşümleri tip düzeyinde hesaplayan eşlenmiş tipleri kuracak.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.