TypeScript Type Narrowing: Tip Daraltma Teknikleri

TypeScript Type Narrowing: Tip Daraltma Teknikleri
Type narrowing, TypeScript'in bir değişkenin tipini daha spesifik bir tipe daraltma sürecidir. Kontrol akışı analizi sayesinde TypeScript, koşullu ifadelerden sonra değişkenlerin tiplerini otomatik olarak daraltır. Bu yazıda tip daraltmanın tüm tekniklerini kapsamlı örneklerle inceleyeceğiz.
typeof Guard
En temel tip daraltma yöntemi, JavaScript'in typeof operatörünü kullanmaktır:
function formatla(deger: string | number | boolean): string {
if (typeof deger === "string") {
// TypeScript burada deger'in string olduğunu bilir
return deger.toUpperCase();
}
if (typeof deger === "number") {
// Burada number
return deger.toFixed(2);
}
// Burada boolean
return deger ? "Evet" : "Hayır";
}
// typeof ile kontrol edilebilen tipler:
// "string", "number", "boolean", "symbol", "undefined",
// "object", "function", "bigint"
function islemYap(girdi: string | number | null): number {
if (typeof girdi === "string") {
return parseInt(girdi, 10);
}
if (typeof girdi === "number") {
return girdi;
}
// girdi: null
return 0;
}
instanceof Guard
Sınıf tabanlı tip daraltma için instanceof operatörü kullanılır:
class ApiHatasi extends Error {
constructor(public statusCode: number, mesaj: string) {
super(mesaj);
}
}
class DogrulamaHatasi extends Error {
constructor(public alanlar: Record<string, string>) {
super("Doğrulama hatası");
}
}
function hataYonet(hata: Error): string {
if (hata instanceof ApiHatasi) {
return `API Hatası (${hata.statusCode}): ${hata.message}`;
}
if (hata instanceof DogrulamaHatasi) {
const hatalar = Object.entries(hata.alanlar)
.map(([alan, mesaj]) => `${alan}: ${mesaj}`)
.join(", ");
return `Doğrulama Hatası: ${hatalar}`;
}
return `Bilinmeyen Hata: ${hata.message}`;
}
// Date kontrolü
function tarihFormatla(deger: string | Date): string {
if (deger instanceof Date) {
return deger.toLocaleDateString("tr-TR");
}
return new Date(deger).toLocaleDateString("tr-TR");
}
in Operatörü ile Daraltma
interface Kus {
tur: string;
uc(): void;
}
interface Balik {
tur: string;
yuz(): void;
}
function hareketEt(hayvan: Kus | Balik): void {
if ("uc" in hayvan) {
hayvan.uc(); // TypeScript: Kus
} else {
hayvan.yuz(); // TypeScript: Balik
}
}
// Daha karmaşık örnek
interface AdminKullanici {
rol: "admin";
yetkiler: string[];
superAdmin: boolean;
}
interface NormalKullanici {
rol: "kullanici";
abonelikTipi: "ucretsiz" | "premium";
}
type Kullanici = AdminKullanici | NormalKullanici;
function kullaniciBilgisi(kullanici: Kullanici): string {
if ("yetkiler" in kullanici) {
return `Admin: ${kullanici.yetkiler.join(", ")}`;
}
return `Kullanıcı: ${kullanici.abonelikTipi} üye`;
}
in operatörü, interface'lerde discriminant property olmadığında tip daraltma için çok kullanışlıdır. Ancak mümkünse discriminated union pattern'ini tercih edin; daha açık ve güvenlidir.
Custom Type Guards
Kendi tip koruma fonksiyonlarınızı is anahtar kelimesi ile tanımlayabilirsiniz:
// User-defined type guard
interface Araba {
tur: "araba";
tekerlekSayisi: 4;
bagajHacmi: number;
}
interface Motosiklet {
tur: "motosiklet";
tekerlekSayisi: 2;
kasaTipi: string;
}
type Arac = Araba | Motosiklet;
// is keyword ile type guard
function arabaDir(arac: Arac): arac is Araba {
return arac.tur === "araba";
}
function aracBilgisi(arac: Arac): string {
if (arabaDir(arac)) {
return `Araba - Bagaj: ${arac.bagajHacmi}L`;
}
return `Motosiklet - Kasa: ${arac.kasaTipi}`;
}
// Null check type guard
function doluDeger<T>(deger: T | null | undefined): deger is T {
return deger !== null && deger !== undefined;
}
const degerler = [1, null, 2, undefined, 3, null];
const temizdegerler = degerler.filter(doluDeger);
// number[] - null ve undefined filtrelendi!
// API yanıt type guard
interface ApiBasarili<T> {
basarili: true;
data: T;
}
interface ApiHata {
basarili: false;
hata: string;
}
type ApiYanit<T> = ApiBasarili<T> | ApiHata;
function basariliMi<T>(yanit: ApiYanit<T>): yanit is ApiBasarili<T> {
return yanit.basarili === true;
}
Assertion Functions
// asserts keyword ile assertion function
function stringOlmalidir(deger: unknown): asserts deger is string {
if (typeof deger !== "string") {
throw new Error(`String beklendi, ${typeof deger} alındı`);
}
}
function islemYap(girdi: unknown): void {
stringOlmalidir(girdi);
// Bu noktadan sonra girdi kesinlikle string
console.log(girdi.toUpperCase());
}
// Non-null assertion function
function doluOlmalidir<T>(
deger: T | null | undefined,
mesaj?: string
): asserts deger is T {
if (deger === null || deger === undefined) {
throw new Error(mesaj ?? "Değer null veya undefined olamaz");
}
}
function kullaniciBul(id: number): string | null {
return id === 1 ? "Tarik" : null;
}
const kullanici = kullaniciBul(1);
doluOlmalidir(kullanici, "Kullanıcı bulunamadı");
console.log(kullanici.toUpperCase()); // Güvenli!
Kontrol Akışı ile Daraltma
// Truthiness narrowing
function isimYazdir(isim: string | null | undefined): void {
if (isim) {
// string (boş string hariç!)
console.log(isim.toUpperCase());
}
}
// Equality narrowing
function karsilastir(x: string | number, y: string | boolean): void {
if (x === y) {
// x ve y sadece string olabilir (ortak tip)
console.log(x.toUpperCase());
}
}
// Assignment narrowing
let deger: string | number;
deger = "merhaba";
console.log(deger.toUpperCase()); // string
deger = 42;
console.log(deger.toFixed(2)); // number
// Never tipine daraltma
type Renk = "kirmizi" | "yesil" | "mavi";
function renkKodu(renk: Renk): string {
switch (renk) {
case "kirmizi": return "#FF0000";
case "yesil": return "#00FF00";
case "mavi": return "#0000FF";
default:
const _kapsamliKontrol: never = renk;
throw new Error(`Bilinmeyen renk: ${_kapsamliKontrol}`);
}
}
Sonuç
Type narrowing, TypeScript'in tip güvenliğinin kalbidir. typeof, instanceof, in operatörü, custom type guard'lar ve assertion function'lar ile geniş tipleri güvenle daraltabilirsiniz. Discriminated union'lar ve exhaustive check pattern'leri ile birleştirildiğinde, çalışma zamanı hatalarını neredeyse tamamen ortadan kaldırabilirsiniz.