Mert Özen Her Satırda Daha İleri
Frontend TypeScript Temel — #12

TypeScript'te Type Assertion, !, satisfies ve as const: Derleyiciye Ne Zaman Ne Söylenir?

Mert Özen 3 Kas 2026 8 dk 1 görüntülenme
TypeScript'te Type Assertion, !, satisfies ve as const: Derleyiciye Ne Zaman Ne Söylenir?

as ile type assertion'ın neyi denetleyip neyi denetlemediğini, ! işaretinin riskini, TS 4.9 ile gelen satisfies'ın hem kontrol edip hem dar tipi korumasını ve as const ile readonly literal'ları görüyoruz.

Sürüm notu: Bu yazıdaki kodlar TypeScript 7.0 ve Node 26 içindir.

Önce Tahmin Et

Üç satır, üç farklı yazım. Hangileri derlenir, hangileri hata verir?

type User = { name: string; age: number };

const first = { name: "Ada" } as User;
const second: User = { name: "Ada" };
const third = "Ada" as User;

Çoğu kişi ilk iki satırın aynı şeyi söylediğini düşünür. Tahminini yap: üçünden hangisi TypeScript'ten geçer? Cevabı as'ın gerçekte ne yaptığını gördükten sonra vereceğiz.

Type Assertion: "Bana Güven" Demek

Şimdiye kadar tipleri ya TypeScript çıkardı ya da biz annotation ile söyledik. Annotation'da TypeScript değeri senin yazdığın type'a karşı denetler. Type assertion ise tam tersini yapar: value as SomeType yazdığında derleyiciye "bu değerin type'ını ben senden iyi biliyorum, bundan sonra onu böyle gör" dersin. Denetleme değil, iddia.

Açılıştaki cevap da buradan çıkıyor. second satırı derlenmez, çünkü annotation literal'ı User'a karşı denetler ve age'in eksik olduğunu görür: Property 'age' is missing in type '{ name: string; }' but required in type 'User'. (TS2741). first satırı ise derlenir. as, eksik property'yi umursamaz; sen "bu bir User" dedin, TypeScript de kabul etti.

third derlenmez, çünkü as'ın da bir sınırı var. İki type arasında hiçbir ortaklık yoksa TypeScript itiraz eder:

error TS2352: Conversion of type 'string' to type 'User' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.

Kural kabaca şu: type'lardan biri diğerine atanabiliyorsa assertion'a izin verilir. { name: string } ile User arasında bu ilişki var, string ile User arasında yok. Mesajın sonundaki öneri, yani önce unknown'a çevirmek ("Ada" as unknown as User), bu sınırı da aşar. O satır derlenir ve tam da bu yüzden kod incelemesinde kırmızı bayraktır.

Unutmaman gereken asıl nokta, assertion'ın çalışma zamanında hiçbir şey yapmamasıdır. Tipler gibi o da silinir; değeri dönüştürmez, kontrol etmez:

type User = { name: string; age: number };

const first = { name: "Ada" } as User;
console.log(first.age.toFixed(0)); // TypeError: Cannot read properties of undefined (reading 'toFixed')

Derleyici sustu, ama program yine çöktü. Bu yüzden "type casting" demek yanıltıcıdır; diğer dillerdeki cast'lerin aksine burada hiçbir dönüşüm olmaz.

as Ne Zaman Meşru?

as yasak değil; bilgin derleyicinin bilgisinden gerçekten fazlaysa doğru araçtır. Tipik örnek, dışarıdan gelen ve şeklini bildiğin veridir:

type Settings = { theme: string; fontSize: number };

const raw = '{"theme":"dark","fontSize":14}';
const settings = JSON.parse(raw) as Settings;
console.log(settings.theme); // dark

JSON.parse any döndürür; as Settings ile o noktadan sonra kodun tip güvenli kalır. Ama dürüst olalım: burada da bir şey denetlenmiyor. JSON'un gerçekten bu şekilde olduğunu ancak çalışma zamanında doğrulayarak bilebilirsin; bunu asenkron kod ve fetch konuşurken unknown ile birlikte göreceğiz. Tarayıcıda DOM element'leriyle çalışırken de as'a sık rastlarsın; onu serinin son yazısında ele alacağız.

Benim kullandığım ölçü şu: bir as yazarken yanına "neden emin olduğumu" açıklayan bir cümle yazabiliyorsan muhtemelen meşrudur. Yazamıyorsan, derleyiciyi değil kendini kandırıyorsundur.

Non-null Assertion: !

!, assertion'ın özel ve çok kısa bir hâlidir: bir ifadenin sonuna eklendiğinde type'tan null ve undefined'ı çıkarır. Strict mod açıkken bu ikisiyle sık karşılaşırsın:

type User = { id: number; name: string };

const users: User[] = [
  { id: 1, name: "Ada" },
  { id: 2, name: "Alan" },
];

const found = users.find((user) => user.id === 3);

// Hata: 'found' is possibly 'undefined'.
console.log(found.name);

find bir şey bulamayabilir, bu yüzden dönüş tipi User | undefined'dır ve TypeScript TS18048 hatasını verir. found!.name yazarsan hata kaybolur, ama bu örnekte id'si 3 olan kullanıcı gerçekten yok. Kod çalıştığında TypeError: Cannot read properties of undefined (reading 'name') alırsın. !, TypeScript'in seni tam da bu hatadan korumak için koyduğu kontrolü kapatır.

Çoğu durumda daha iyi bir yol vardır: narrowing yazısında gördüğümüz if (found) kontrolü ya da found?.name ?? "unknown" gibi bir varsayılan değer. !'yı yalnızca değerin orada olduğunu kodun yapısı garanti ettiği hâlde TypeScript'in bunu göremediği yerlerde kullan.

satisfies: Hem Kontrol Hem Dar Tip

Annotation ile assertion arasında uzun süre bir boşluk vardı. TS 4.9 ile gelen satisfies bu boşluğu doldurur. Önce sorunu görelim:

type Color = string | [number, number, number];
type Palette = Record<"primary" | "accent", Color>;

const palette: Palette = {
  primary: "#0055ff",
  accent: [255, 120, 0],
};

// Hata: Property 'toUpperCase' does not exist on type 'Color'.
palette.primary.toUpperCase();

Annotation işini yaptı: object'i denetledi. Ama karşılığında bilgiyi de sildi. primary'nin bir string olduğunu biz görüyoruz, TypeScript ise artık yalnızca "bir Color" görüyor. Şimdi aynı object'i satisfies ile yazalım:

type Color = string | [number, number, number];
type Palette = Record<"primary" | "accent", Color>;

const palette = {
  primary: "#0055ff",
  accent: [255, 120, 0],
} satisfies Palette;

console.log(palette.primary.toUpperCase()); // #0055FF
console.log(palette.accent.map((channel) => channel / 255).length); // 3

satisfies object'i Palette'e karşı denetler, ama değişkenin type'ını değiştirmez. palette'in type'ı { primary: string; accent: [number, number, number]; } olarak kalır. Denetim de gerçekten çalışır: accent'i acent diye yazarsan tsc Object literal may only specify known properties, but 'acent' does not exist in type 'Palette'. Did you mean to write 'accent'? der (TS2561); accent'i tamamen silersen TS2741 ile eksik property'yi raporlar.

as const: Readonly Literal'lar

as const'a union ve literal tipler yazısında kısaca bakmıştık. Adı as ile başlasa da tehlikeli bir assertion değildir; hiçbir şeyi iddia etmez, sadece TypeScript'ten "bu değeri olabilecek en dar ve değişmez hâliyle gör" diye ister:

const config = {
  env: "production",
  retries: 3,
  regions: ["eu", "us"],
} as const;

// Hata: Cannot assign to 'env' because it is a read-only property.
config.env = "staging";

// Hata: Property 'push' does not exist on type 'readonly ["eu", "us"]'.
config.regions.push("asia");

config'in type'ı artık { readonly env: "production"; readonly retries: 3; readonly regions: readonly ["eu", "us"]; }. Üç şey birden oldu: string ve number'lar literal type'a daraldı, her property readonly oldu, array ise readonly bir tuple'a dönüştü. Etki iç içe tüm seviyelere iner.

as const ve satisfies Birlikte

İkisi çok iyi anlaşır. as const değerleri dondurur ve daraltır, satisfies da sonucun bir şekle uyduğunu denetler:

type Route = { path: string; title: string };

const routes = {
  home: { path: "/", title: "Home" },
  about: { path: "/about", title: "About" },
} as const satisfies Record<string, Route>;

const homePath = routes.home.path; // type'ı: "/"
console.log(homePath); // /

Bir route'ta title'ı tittle diye yazarsan TS2561 hatası alırsın; doğru yazdığında ise her path kendi literal type'ını korur. Sıra önemli: önce as const, sonra satisfies. Uygulamadaki sabit tablolar, menü tanımları, ayar listeleri için en sevdiğim kalıp budur.

Hangisi Ne Zaman?

Annotation (const x: T = …): değişkenin gerçekten T olmasını, yani ileride T'nin izin verdiği her değeri alabilmesini istiyorsan. Denetler, ama değişkenin type'ı T olur.

satisfies T: değerin T'ye uyduğundan emin olmak ama çıkarılan dar type'ı kaybetmemek istiyorsan. Denetler ve bilgiyi korur.

as const: değerlerin kendisi önemliyse ve değişmeyecekse. Denetlemez, daraltır ve dondurur; satisfies ile eşleştirilebilir.

as T ve !: derleyicinin göremediği bir bilgiye sahipsen ve bunu bir cümleyle savunabiliyorsan. İkisi de denetimi kapatır; ilk üç aracın hiçbiri işini görmüyorsa son çare olarak kalsın.

Sık Düşülen Tuzaklar

Birinci tuzak, as'ı hata susturucu olarak kullanmak. Kırmızı çizgiyi gördüğünde ilk refleks as any ya da as unknown as X olmamalı. Hata çoğu zaman gerçek bir sorunu işaret eder; assertion o sorunu çözmez, çalışma zamanına erteler.

İkinci tuzak, as const'un çalışma zamanında da koruma sağladığını sanmak. readonly yalnızca derleme zamanında vardır; JavaScript'e derlenen kodda config sıradan, değiştirilebilir bir object'tir. Çalışma zamanında da değişmezlik istiyorsan Object.freeze gerekir, o da yalnızca ilk seviyeyi dondurur.

Üçüncü tuzak, !'yı alışkanlık hâline getirmek. Bir kod tabanında ! sayısı arttıkça strict modun sana sağladığı null güvencesi sessizce kaybolur. Her !'yı küçük bir borç gibi gör ve mümkünse bir narrowing kontrolüyle öde.

Küçük Bir Deneme

★☆☆ Derlenir mi, tahmin et: const count = "42" as number;. Peki const count = "42" as unknown as number;? İkinci satır derlendiyse, count.toFixed(1) çalışma zamanında ne yapar?

★★☆ palette örneğinde satisfies Palette yerine as Palette yaz. accent'i silmeyi dene; hata alıyor musun? satisfies ile farkını kendi cümlelerinle açıkla.

★★☆ Type'ı ne olur: const sizes = ["small", "medium", "large"] as const; içindeki sizes? Aynı array'i as const olmadan yazdığında type nasıl değişiyor? Editörde üzerine gelerek karşılaştır.

★★★ users.find örneğini ! kullanmadan, kullanıcı bulunamadığında konsola User not found yazacak şekilde yeniden yaz.

Bu yazıda TypeScript'in davranışını kod içinden, ifade ifade yönlendirdik. Bir sonraki yazıda aynı ayarı proje düzeyinde yapacağız: tsconfig.json'u açıp strict ailesini, target ve module gibi temel ayarları, TypeScript 6 ve 7 ile gelen yeni varsayılanları ve 7.0'da kaldırılan seçenekleri tek tek konuşacağız.