Bölüm 4 / 10
Union ve tip daraltma
Union, intersection, literal tipler, type guard.
13 dakikalık okuma
TypeScript'in günlük pratiği büyük ölçüde iki hareketten
oluşur: bir değerin olabileceği hâlleri birleşimle ifade etmek, sonra kodun her
dalında bu hâlleri daraltarak tek tek ele almak. Derleyici bunu yaparken senin
if'lerini okur — buna kontrol akışı analizi denir ve TypeScript'i "tip
yazdığın JavaScript"ten "seninle birlikte düşünen derleyici"ye çeviren şey budur. Bu
ders 8. hattın iki senedini de öder: doğruluk değeri tuzakları ve catch
içindeki hata sınıfları.
01Ne öğreneceksin
- Birleşim (union) tiplerinde yalnız ortak üyelere erişilebilmesi kuralı
- Literal tipler, genişletme ve
as const'un daraltıcı gücü - Daraltma araçları:
typeof, eşitlik,in,instanceof— vetypeof nulltuzağının dönüşü - Doğruluk değeri daraltması ve 0/boş dize tuzağı;
?? ?. != nullüçlüsünün tiple bütünleşmesi catch (e: unknown)içinde hata daraltma — 8. hattınApiHatasisenedi- Ayırt edici birleşim: durum modellemenin üretim kalıbı +
neverile tüketilmişlik denetimi - Kullanıcı tanımlı tip koruyucular (
x is T) ve yalan söyleme riskleri - Akış analizinin sınırları: geri çağrılar, mutasyon, fonksiyon sınırları
02Birleşim: "şunlardan biri"
type Kimlik = number | string;
function biletBul(id: Kimlik) {
id.toFixed(2); // TS2339 — ya string ise?
id.toUpperCase(); // TS2339 — ya number ise?
id.toString(); // OLUR — İKİSİNDE DE var
}
Kural nettir: birleşim tipli değer üzerinde yalnız tüm üyelerde ortak olan şeylere erişebilirsin. Gerisi için önce hangi üyede olduğunu kanıtlaman — daraltman — gerekir. Bu, JavaScript'te zaten yazdığın savunmacı kontrollerin ta kendisidir; fark, TypeScript'in bu kontrolleri okuyup tipe işlemesi ve unuttuğunda seni durdurmasıdır.
03Literal tipler: değerin kendisi tip olunca
2. derste görmüştün: "asc" yalnız bir değer değil, aynı zamanda bir
tiptir — tek elemanlı bir tip. Literallerin birleşimi, kümesi sınırlı değerleri
ifade etmenin standart yoludur:
type Yon = "asc" | "desc";
type ZarDegeri = 1 | 2 | 3 | 4 | 5 | 6;
type Cevap = true | "belki"; // karışık da olur
function sirala(alan: string, yon: Yon) { … }
sirala("ad", "desc"); // editör iki seçeneği de TAMAMLAR
sirala("ad", "Desc"); // TS2345 — yazım/büyük harf hatası anında
Genişletme kuralını hatırla: let yon = "asc" → string;
const yon = "asc" → "asc". Nesne içindeki alanlar ise
const ile tanımlansa da genişler — çünkü alan sonradan değiştirilebilir:
const istek = { yontem: "GET" }; // yontem: string — GENİŞLEDİ
fetch(url, istek); // yontem: "GET" bekleyen tipe UYMAZ
const istek2 = { yontem: "GET" } as const; // yontem: "GET" — dar kaldı
const istek3 = { yontem: "GET" as const }; // tek alanı daraltmak da mümkün
as const bu yüzden literal ağırlıklı yapılandırma nesnelerinin sadık
arkadaşıdır. (Şablon literal tipleri — `kariyer:${string}` gibi desenli
dizeler — 6. dersin utility konularıyla birlikte gelecek.)
04Daraltma araçları
Derleyici şu yapıları görünce tipi dal bazında daraltır:
function isle(deger: string | number | Date | null) {
if (typeof deger === "string") {
deger.toUpperCase(); // burada: string
} else if (typeof deger === "number") {
deger.toFixed(2); // burada: number
} else if (deger instanceof Date) {
deger.getTime(); // burada: Date
} else {
deger; // burada: null — GERİYE KALAN
}
}
typeofilkeller için. 8. hattın 13. dersindeki tuzak tip sisteminde de yaşar:typeof deger === "object"dalınanullda düşer — derleyici bunu bilir ve dalın tipiniNesne | nullolarak tutar; null'u ayrıca elemen gerekir. Tuzağı bilen dil, tuzağı unutturmayan derleyiciye dönüşmüştür.- Eşitlik daraltması:
deger === "hazir",a === bkarşılaştırmaları iki tarafın tipini keser. inoperatörü nesne birleşimlerini üyeye göre ayırır:if ("yuzgec" in hayvan). Yapısal sistemde işe yarar ama kırılgandır — asıl kalıp 6. bölümdeki ayırt edici birleşimdir.instanceofsınıf örnekleri için — ve en önemli sahnesicatchbloğudur:
class ApiHatasi extends Error { // 8. hat 17. dersten
constructor(public durum: number, mesaj: string) { super(mesaj); }
}
try {
await veriGetir("/api/basvurular");
} catch (e) { // e: unknown (strict)
e.message; // TS18046 — kanıt yok!
if (e instanceof ApiHatasi) {
console.error(`HTTP ${e.durum}: ${e.message}`); // ApiHatasi
} else if (e instanceof Error) {
console.error(e.message); // genel Error
} else {
console.error("Bilinmeyen hata:", e); // throw "yazı" da mümkün!
}
}
JavaScript'te her şey fırlatılabilir (throw "hata" bile);
useUnknownInCatchVariables bu gerçeği tipe döker: catch
değişkeni unknown'dır ve 8. hatta alışkanlık edindiğin
instanceof merdiveni artık derleyici tarafından zorunlu kılınır.
Hata sınıfı hiyerarşisi kurmanın (17. derste başlayan) karşılığı burada tam olarak
ödenir.
05Doğruluk değeri: en sevilen ve en tehlikeli daraltıcı
function yazdir(mesaj: string | null) {
if (mesaj) {
mesaj.toUpperCase(); // string — null ELENDİ… ama yalnız o mu?
}
}
Çalışır, derlenir, çoğu zaman doğrudur — ve 8. hattın 13. dersindeki tuzağı aynen
içerir: if (mesaj) yalnız null'u değil, boş dizeyi de
eler. string | null için bu genellikle zararsızdır; ama tip
number | null olduğunda 0, string | undefined
olduğunda "" sessizce "yok" muamelesi görür:
function indirimYaz(oran: number | null) {
if (oran) { … } // %0 indirim JS'de falsy — YANLIŞ dala gider
if (oran !== null) { … } // doğru: yalnız null elendi, 0 içeride
if (oran != null) { … } // null VE undefined'ı birlikte eler (bilinçli ==)
}
Karar kalıbı: "var mı?" sorusunun cevabı != null, "dolu mu?"
sorusunun cevabı doğruluk değeri. İki soru farklıdır; hangisini sorduğunu bilerek
yaz. ?? ve ?. bu ayrımın operatör hâlidir ve tiple kusursuz
bütünleşir: oran ?? 0.2 ifadesinin tipi number'dır (null
ihtimali düştü), kullanici?.adres?.sehir ifadesinin tipi
string | undefined'dır — zincirin kısa devresi tipe yansır.
06Ayırt edici birleşim: durumun üretim kalıbı
Nesne birleşimlerini daraltmanın en sağlam yolu, her üyeye ortak adlı, literal tipli bir alan koymaktır. 8. hatta beş ekran durumunu (boş · yükleniyor · hata · dolu · kısmi) elle yönetmiştin; tiplenmiş hâli şudur:
type AramaDurumu =
| { tur: "bos" }
| { tur: "yukleniyor"; imza: AbortSignal }
| { tur: "hata"; hata: ApiHatasi }
| { tur: "dolu"; kayitlar: Basvuru[]; toplam: number };
function ciz(durum: AramaDurumu) {
switch (durum.tur) {
case "bos": return bosEkran();
case "yukleniyor": return iskelet();
case "hata": return hataKutusu(durum.hata); // hata alanı BURADA var
case "dolu": return tablo(durum.kayitlar); // kayitlar BURADA var
default: {
const kalan: never = durum; // tüm durumlar tüketildiyse durum = never
throw new Error(`Beklenmeyen durum: ${JSON.stringify(kalan)}`);
}
}
}
Bu kalıbın üç kazancı vardır:
- İmkânsız durumlar temsil edilemez. "Hem yükleniyor hem kayıtlar dolu" gibi
çelişkili hâller tek nesnede
durum: boolean; kayitlar?: …; hata?: …yazınca mümkündür; birleşimde yazılamaz bile. Tip tasarımının altın kuralı: geçersiz hâli tipte imkânsız kıl, kontrol etmek zorunda kalma. - Her dalda doğru alanlar açılır.
durum.hata'ya yalnız"hata"dalında erişilir; başka dalda derleme hatasıdır. - Tüketilmişlik denetimi:
default'takineverataması sayesinde birleşime yarın{ tur: "kismi" }eklendiğinde buswitchderlenmez —TS2322: Type '{ tur: "kismi"; }' is not assignable to type 'never'. "Yeni durum ekledik, üç ekrandan birini güncellemeyi unuttuk" hatası sınıf olarak ortadan kalkar. Angular ve React'te durum yönetiminin (10-11. hat) bel kemiği bu kalıptır; Redux eylemleri, RxJS olayları hep ayırt edici birleşimdir.
07Kendi koruyucunu yazmak: x is T
Daraltma mantığı bir fonksiyona taşınacaksa, sıradan boolean dönüş
yetmez — derleyici fonksiyonun içini çağrı yerinde takip etmez. Dönüş tipine
tip yüklemi (type predicate) yazılır:
function apiHatasiMi(e: unknown): e is ApiHatasi {
return e instanceof ApiHatasi;
}
catch (e) {
if (apiHatasiMi(e)) e.durum; // daraldı — yüklem çağrıya taşındı
}
// Doğrulama tarzı koruyucu — dış veri sınırında (5. derste büyüyecek):
function basvuruMu(v: unknown): v is Basvuru {
return typeof v === "object" && v !== null &&
"id" in v && typeof (v as { id: unknown }).id === "number";
}
Büyük uyarı: tip yüklemi bir beyandır — derleyici gövdenin yüklemi
gerçekten kanıtladığını denetlemez. return true yazsan da imza
geçerlidir ve yalan söyleyen koruyucu, as'ten daha sinsidir çünkü
güvenilir görünür. Kural: koruyucu gövdesi kısa, mekanik ve alan alan kontrol eden
kod olmalı; "herhâlde budur" sezgisi koruyucuya yazılmaz. Akrabası iddia
fonksiyonudur: function dogrula(v: unknown): asserts v is Basvuru —
dönerse tip daralır, dönmezse fırlatır; doğrulama katmanlarında (9. ders) kullanılır.
08Akış analizinin sınırları
Daraltma, derleyicinin kodu satır satır izleyebildiği yere kadar sürer. Üç yerde biter — üçü de günlük hayatta karşına çıkacak:
let deger: string | null = al();
// 1) GERİ ÇAĞRI SINIRI
if (deger !== null) {
islemler.push(() => deger.toUpperCase());
// TS18047 OLABİLİR: geri çağrı SONRA çalışacak; deger o ana dek değişebilir.
// let yerine const kullanılırsa derleyici daraltmayı korur.
}
// 2) MUTASYON SIFIRLAR
if (deger !== null) {
deger = belkiNull(); // daraltma bozuldu
deger.toUpperCase(); // TS18047 — haklı olarak
}
// 3) FONKSİYON SINIRI
function dolu(x: string | null): boolean { return x !== null; }
if (dolu(deger)) deger.toUpperCase();
// TS18047 — sıradan boolean dönüş, bilgiyi çağrıya TAŞIMAZ (çözüm: x is string)
Pratik çıkarım üçlüdür: daraltılacak değeri const'a al
(const d = deger; if (d !== null) … — const değişemeyeceği
için daraltma geri çağrılarda bile korunur), daraltma sonrası mutasyondan kaçın
ve yardımcı kontrolleri tip yüklemiyle yaz. Bunlar derleyici kaprisi değildir;
derleyici, asenkron çalışacak geri çağrının eski kontrole güvenemeyeceğini senden iyi
hatırlamaktadır — 8. hattın yarış koşulu dersinin tip sistemindeki yankısı.
09Mini alıştırma — Derleyiciyle birlikte düşün
Yaklaşık 20 dakika · TypeScript Playground, strict açık
- Ortak üye kuralı:
number | stringparametreli fonksiyondatoFixed,toUpperCasevetoStringçağır. Hangisi neden geçti? - Daraltmayı izle: aynı fonksiyonu
typeofmerdiveniyle böl ve her dalda parametrenin üzerine gelip tipini oku.elsedalında ne kaldı? typeof null:string | Nokta | nulltipli değeritypeof deger === "object"ile dalla; dalın içinde tipinNokta | nullkaldığını gör. Null'u nasıl elersin?- 0 tuzağı:
indirimYaz(oran: number | null)yaz;if (oran)ileif (oran != null)dallarında tipleri karşılaştır veindirimYaz(0)için hangisinin yanlış davrandığını söyle. - Zincir tipi:
kullanici?.adres?.sehirifadesinin tipini oku; sonra?? "bilinmiyor"ekle ve tipin nasıl değiştiğini gör. - catch merdiveni:
ApiHatasi extends Errorkur; birtry/catchiçinde öncee.message'a doğrudan eriş (TS18046), sonrainstanceofmerdivenini kur. Sonelse'e hangi değerler düşebilir? - Ayırt edici birleşim: bu dersteki
AramaDurumu'nu yaz,switch+default: neverkur. Sonra birleşime{ tur: "kismi" }ekle — hata tam olarak hangi satırda, ne diyerek çıktı? Bu hatanın üretimde önlediği senaryoyu bir cümleyle yaz. - İmkânsız hâl: aynı durumu tek nesneyle
(
yukleniyor: boolean; kayitlar?: …; hata?: …) modelle ve "hem hata hem dolu" nesnesini yaz — derleyici itiraz etti mi? Birleşim modelinde aynı şeyi yazmayı dene. - Yalancı koruyucu: gövdesi
return trueolane is ApiHatasikoruyucusu yaz vee.durum'a eriş. Derleyici seni korudu mu? Çalıştırınca ne olur? Çıkardığın kural ne? - Akış sınırı:
let deger: string | nullile 8. bölümdeki üç senaryoyu tek tek yeniden üret; her birinde hatayı gör, sonraconst'a alma ve tip yüklemi çözümleriyle geçir.
10Kendini sına
Ayırt edici birleşim (discriminated union) nedir? "Tek nesne + isteğe bağlı alanlar" modeline neden tercih edilir?
Her üyesi, ortak adlı ve literal tipli bir ayırt edici alan
(tur: "hata" gibi) taşıyan nesne birleşimidir; switch (durum.tur)
yazıldığında derleyici her dalda ilgili üyeye daraltır. Üç üstünlüğü vardır.
(1) İmkânsız hâller yazılamaz: tek nesnede yukleniyor: boolean +
isteğe bağlı kayitlar/hata alanları "hem yükleniyor hem
hatalı" gibi çelişkileri temsil edebilir ve her kullanım yeri savunmacı kontrole
mahkûm olur; birleşimde geçersiz kombinasyon tipte var olmadığı için kontrol de
gerekmez — "geçersiz durumu imkânsız kıl" ilkesi. (2) Alan erişimi dal bazında
güvenlidir: durum.hata'ya yalnız hata dalında erişilir.
(3) Tüketilmişlik denetimi: default dalında kalan değeri
never tipli değişkene atarsan, birleşime yeni üye eklendiğinde bu atama
derleme hatası verir — "yeni durumu üç ekrandan birinde unutmak" hatası derleme
anına çekilir. Ekran durumları, Redux eylemleri, RxJS olayları ve API yanıt
modelleri bu kalıpla yazılır; 10-11. hatların durum yönetimi buna dayanır.
if (deger) daraltması hangi durumda hataya yol açar? Doğru kalıp nedir?
Doğruluk değeri daraltması null ve
undefined'la birlikte tüm falsy değerleri eler: 0,
"", NaN, false. Tip string | null
gibi "falsy değeri anlamlı olmayan" bir birleşimse sorun çıkmaz; ama
number | null'da 0 (yüzde sıfır indirim, sıfır bakiye) ve
string | undefined'da boş dize meşru değerlerdir ve
if (deger) onları sessizce "yok" dalına atar — derleyici de itiraz etmez
çünkü daraltma tip açısından tutarlıdır; hata mantık düzeyindedir. Doğru kalıp soruyu
netleştirmektir: "değer var mı?" sorusu deger != null ile sorulur
(bilinçli çift eşittir: null ve undefined'ı birlikte eler, 0 ve boş dizeyi içeride
tutar); "değer dolu/anlamlı mı?" sorusu doğruluk değeriyle sorulabilir.
?? aynı ayrımın operatörüdür: oran ?? 0.2 yalnız
null/undefined'da varsayılana düşer, oran || 0.2 ise 0'da da düşer —
8. hattın 13. dersindeki kural tip sisteminde aynen yaşar ve ?? sonrası
tipten null ihtimalinin düşmesi, derleyicinin bu ayrımı anladığının kanıtıdır.
catch bloğunda yakalanan değerin tipi neden unknown'dır ve nasıl ele alınır?
Çünkü JavaScript'te throw her değeri fırlatabilir:
Error türevleri dışında dize, sayı, düz nesne, hatta
undefined. Ayrıca hata, senin kodundan değil üçüncü taraf kütüphaneden
ya da çalışma ortamından da gelebilir; tip sistemi hangi tipin fırlayacağını izlemez
(Java'daki checked exceptions TypeScript'te yoktur). useUnknownInCatchVariables
(strict ailesinden) bu gerçeği tipe döker: catch (e) içinde
e: unknown'dır ve e.message'a doğrudan erişim
TS18046 verir. Doğru ele alış, daraltma merdivenidir: önce en özel kendi
hata sınıfların (e instanceof ApiHatasi — 8. hat 17. derste kurulan
sınıf burada meyvesini verir: e.durum gibi alanlar daralınca açılır),
sonra e instanceof Error ile genel hata, en sonda "bilinmeyen değer"
dalı. Tekrarlanan merdiven bir tip yüklemine (e is ApiHatasi) ya da
"her şeyi Error'a çevir" yardımcısına çıkarılır. Yanlış ele alış
catch (e: any) yazmaktır — derleyicinin verdiği korumayı kapı önünde
geri teslim etmek.
Tip yüklemi (x is T) ne işe yarar, riski nedir? Daraltma hangi durumlarda "kaybolur"?
Sıradan boolean döndüren kontrol fonksiyonu, çağrı
yerinde daraltma yapmaz — derleyici fonksiyon gövdesini çağrı noktasında izlemez.
Dönüş tipini e is ApiHatasi biçiminde yazmak, "true dönersem argüman bu
tiptedir" bilgisini imzaya taşır ve if (apiHatasiMi(e)) artık daraltır.
Risk: yüklem bir beyandır, derleyici gövdenin onu kanıtladığını doğrulamaz;
return true yazan bir koruyucu tip sistemini sessizce zehirler ve
güvenilir göründüğü için as'ten sinsidir. Kural: koruyucu gövdesi alan
alan, mekanik kontrol yapmalı. İkinci soru — daraltma üç sınırda kaybolur:
(1) geri çağrı sınırı: let değişkenin daraltması, sonra
çalışacak bir fonksiyonun içinde korunmaz çünkü değer o ana dek değişebilir; çözüm
değeri const'a almaktır. (2) mutasyon: daraltmadan sonra
değişkene yeni atama yapılırsa analiz haklı olarak sıfırlanır. (3) fonksiyon
sınırı: kontrolü başka fonksiyona taşımak bilgiyi taşımaz — tip yüklemi tam bu
boşluk için vardır. Bu davranışlar kapris değildir; özellikle geri çağrı kuralı,
asenkron kodda eski kontrole güvenmenin gerçek bir yarış koşulu hatası olduğunu
hatırlatır.
11Sınav köşesi
Yazılıda nasıl sorulur
(1) Union type nedir; üzerinde hangi üyelere erişilebilir? (2) Literal type
nedir; ne işe yarar? (3) Type narrowing nedir; hangi yapılarla yapılır?
(4) typeof, instanceof, in ile daraltma
örnekleri. (5) Discriminated union nedir; nasıl kurulur? (6) Exhaustiveness check
(never ile) nasıl yapılır? (7) Type guard / type predicate
(x is T) nedir? (8) Assertion function (asserts) nedir?
(9) catch değişkeni neden unknown'dır?
(10) Truthiness narrowing'in riski nedir? (11) as const daraltmaya
nasıl yardım eder? (12) Intersection type ile union type farkı.
Sözlüde nasıl anlatılır — 2 dakikalık taslak
Düzen: (1) Çerçeve — "günlük TypeScript iki hamledir: olası hâlleri birleşimle
yazmak, her dalda daraltmak. Birleşimde yalnız ortak üyelere erişilir; gerisi kanıt
ister ve kanıtı derleyici if'lerimden okur — kontrol akışı analizi."
(2) Araçlar — "typeof ilkellere (null'un 'object' çıkması tipte de
yaşar), instanceof sınıflara — özellikle catch'te, çünkü
değişken unknown'dır ve hata sınıfı merdiveni zorunludur;
in üye kontrolüne." (3) Üretim kalıbı — "durumları ayırt edici
birleşimle modellerim: imkânsız hâller yazılamaz, her dalda doğru alanlar açılır,
default'taki never ataması yeni durum eklenince derlemeyi
kırar — ekran/eylem modellemenin bel kemiği." (4) Kıdem vuruşu — "doğruluk değeri
daraltması 0 ve boş dizeyi de eler; 'var mı' sorusunu != null ile
sorarım. Tip yüklemi beyandır, gövdesini derleyici doğrulamaz — koruyucular mekanik
yazılır. Daraltma geri çağrıda ve mutasyonda kaybolur; değeri const'a
alırım."
