TypeScript'te tip güvenliği nerede biter?

TypeScript'te tip güvenliği nerede biter?

Derleyici geçti diye program doğru olmuyor. any yazmadan da tip sistemine yalan söyleyebileceğin beş yer, strict'in yakalamadıkları ve deliklerin sınırda nasıl kapatıldığı.

Furkan Özay·10 Haziran 2025·6 dk okuma·2 Eyl 2026'de güncellendi

TypeScript'in ne olduğunu anlatan yazı çok. Bu yazı başka bir soruyu soruyor: strict: true yazdın, any kullanmadın, derleyici geçti — programın doğru mu?

Değil. Ve nerede olmadığını bilmek, tip sistemini gerçekten kullanmakla ona gereğinden fazla güvenmek arasındaki fark.

Temel gerçek: tipler çalışma anında yok

TypeScript derlendiğinde tipler silinir. Tarayıcıya ya da Node'a inen şey düz JavaScript; hiçbir tip kontrolü çalışma anına taşınmaz.

TypeScript
type User = { id: number; name: string };
 
function greet(user: User) {
	return `Merhaba ${user.name}`;
}

Bu fonksiyon derlendiğinde geriye şu kalıyor:

JavaScript
function greet(user) {
	return `Merhaba ${user.name}`;
}

Yani greet'e çalışma anında null gelirse hiçbir şey onu durdurmuyor. TypeScript'in verdiği güvence şu: kodun içinden yanlış çağrı yapamazsın. Kodun dışından gelen her şey — API yanıtı, form verisi, localStorage, ortam değişkeni — bu güvencenin dışında.

Aşağıdaki beş delik hep aynı yerden çıkıyor.

Delik 1: as bir kontrol değil, bir iddia

En sık ve en tehlikeli olanı bu.

TypeScript
const res = await fetch("/api/user");
const user = (await res.json()) as User;
 
console.log(user.name.toUpperCase());

Burada hiçbir doğrulama yok. as User demek "bunun User olduğunu ben söylüyorum" demek. API { error: "not found" } dönerse derleyici hiçbir şey demiyor, satır çalışma anında Cannot read properties of undefined ile patlıyor.

⚠️as'i ne zaman kullanmalı?

Yalnız derleyicinin bilemeyeceği bir şeyi sen gerçekten biliyorsan: document.getElementById("x") as HTMLInputElement gibi. Dışarıdan gelen veride as kullanıyorsan, doğrulama yapmıyorsun — doğrulama yaptığını sanıyorsun.

Delik 2: JSON.parse her şeye any diyor

TypeScript
const raw = localStorage.getItem("settings");
const settings = JSON.parse(raw!); // any
settings.theme.colors.primary; // derleyici sessiz

JSON.parse'ın dönüş tipi any. any de tip sisteminin kapalı olduğu yer demek: any üzerinden yapılan her şey kontrolsüz geçiyor ve bu, atandığı yere kadar yayılıyor.

fetch yanıtı da aynı: res.json() tipi Promise<any>.

💡Küçük ama etkili alışkanlık

JSON.parse'ın sonucunu unknown olarak al. unknown da her şeyi kabul ediyor ama kullanmadan önce daraltmanı zorunlu kılıyor:

TypeScript
const settings: unknown = JSON.parse(raw ?? "{}");
// settings.theme  → derleme hatası, önce doğrulaman gerekiyor

Delik 3: ! operatörü

Ünlem işareti "bu null ya da undefined değil" demek — yine bir iddia.

TypeScript
const el = document.querySelector(".modal")!;
el.classList.add("open"); // element yoksa çalışma anında patlar
 
const user = users.find((u) => u.id === id)!;

İkinci satır özellikle sinsi: find sonucu bulunamazsa undefined ama ! bunu susturuyor. Doğrusu kontrol etmek:

TypeScript
const user = users.find((u) => u.id === id);
if (!user) {
	return notFound();
}

Delik 4: strict dizi indekslemesini yakalamıyor

Bunu deneyerek doğruladım. Şu kod strict: true altında hatasız geçiyor:

TypeScript
const arr: string[] = [];
const first = arr[0];
console.log(first.length); // çalışma anında: Cannot read properties of undefined

TypeScript arr[0]'ın tipini string sayıyor, oysa dizi boş. Aynı şey Record<string, T> erişiminde de geçerli.

Çözüm strict'in içinde değil, ayrı bir bayrakta:

🧪tsconfig.json
JSON
{
	"compilerOptions": {
		"strict": true,
		"noUncheckedIndexedAccess": true
	}
}

Aynı kod artık hata veriyor: 'first' is possibly 'undefined'.

Bayrağı açtığında mevcut kodda çok sayıda hata çıkabilir — ama çıkan her hata, gerçekten var olan bir çalışma anı riskini gösteriyor.

💬strict'in içinde olmayan diğerleri

strict: true bir paket, ama hepsini kapsamıyor. Şunlar ayrıca açılıyor:

  • noUncheckedIndexedAccess — dizi/nesne indekslemesi | undefined alır
  • exactOptionalPropertyTypes{ a?: string } alanına açıkça undefined atamayı engeller
  • noImplicitOverride — sınıfta override yazmayı zorunlu kılar
  • noPropertyAccessFromIndexSignature — index imzasına nokta ile erişimi engeller

İlk ikisi yeni bir projede baştan açılmalı; sonradan açmak göç işi.

Delik 5: process.env ve yapılandırma

TypeScript
const dbUrl = process.env.DATABASE_URL; // string | undefined
const client = new Client({ url: dbUrl! }); // yine iddia

Ortam değişkeni bir kod değil, bir dağıtım ayrıntısı. Değişken sunucuda tanımlanmamışsa hata en kötü yerde çıkıyor: uygulamanın açılışında değil, o kod yolunun ilk çalıştığı anda. Doğrusu açılışta bir kez doğrulamak:

TypeScript
function requireEnv(name: string): string {
	const value = process.env[name];
	if (!value) {
		throw new Error(`Eksik ortam değişkeni: ${name}`);
	}
	return value;
}
 
export const env = {
	databaseUrl: requireEnv("DATABASE_URL"),
	resendApiKey: requireEnv("RESEND_API_KEY"),
};

Uygulama eksik yapılandırmayla açılmıyor — hata, sebebini söyleyerek ve doğru zamanda çıkıyor.

Delikleri sınırda kapatmak

Beş deliğin ortak noktası şu: hepsi verinin sisteme girdiği yerde. O yüzden çözüm de tek: sınırlarda çalışma anı doğrulaması yap, içeride tiplere güven.

Zod bunu tek adımda çözüyor — hem doğruluyor hem de TypeScript tipini şemadan türetiyor, yani tipi iki kez yazmıyorsun:

🧪Sınırda doğrulama
TypeScript
import { z } from "zod";
 
const UserSchema = z.object({
	id: z.number(),
	name: z.string(),
	email: z.string().email(),
});
 
// Tip şemadan türüyor — ayrıca `type User` yazmıyorsun
type User = z.infer<typeof UserSchema>;
 
export async function getUser(id: number): Promise<User> {
	const res = await fetch(`/api/users/${id}`);
	const json: unknown = await res.json();
 
	// Burada gerçekten kontrol ediliyor; `as` gibi bir iddia değil
	return UserSchema.parse(json);
}

Bu satırdan sonra user.name'in string olduğunu biliyorsun, varsaymıyorsun. Aynı yaklaşım form verisi, webhook gövdesi ve localStorage için de geçerli.

İlgili yazı: Next.js'te form validasyonu: client ve server side

Eskimiş iki alışkanlık

React.FC kullanma. Bir dönem standarttı; artık gereksiz. Props'u doğrudan yazmak hem daha kısa hem generic bileşenlerde sorun çıkarmıyor:

TSX
// Eski
const Button: React.FC<ButtonProps> = ({ children }) => { /* ... */ };
 
// Şimdi
function Button({ children }: ButtonProps) { /* ... */ }

enum yerine as const düşün. enum derlendiğinde çalışma anında kod üretiyor — tipler siliniyor ama enum kalıyor. Union tipi ise hiçbir şey üretmiyor:

TypeScript
// Çalışma anında nesne üretir
enum LogLevel { DEBUG = "debug", INFO = "info" }
 
// Hiçbir şey üretmez, aynı işi görür
const LOG_LEVELS = ["debug", "info", "warn", "error"] as const;
type LogLevel = (typeof LOG_LEVELS)[number];
⚠️const enum'a dikkat

"const enum daha performanslı" tavsiyesi dolaşıyor, ama bir kısıtı var: isolatedModules açıkken — ki Next.js bunu zorunlu tutuyor — bir .d.ts içindeki ambient const enum'a erişmek hata veriyor:

TS2748: Cannot access ambient const enums when 'isolatedModules' is enabled.

Kendi dosyanda tanımlayıp import ettiğin const enum çalışıyor; kütüphaneden gelen ambient olan çalışmıyor. Bu ayrımı bilmiyorsan hatanın nereden geldiğini bulmak zaman alıyor.

Özet

📋Kontrol listesi
  • Tipler çalışma anında yok; güvence yalnız kendi kodunun içinde
  • as ve ! birer iddia; dış veride kullanılırsa doğrulama yapılmamış demektir
  • JSON.parse ve res.json() any döner — unknown olarak al, daraltmaya zorla
  • strict dizi indekslemesini yakalamıyor; noUncheckedIndexedAccess ayrıca açılmalı
  • Ortam değişkenlerini açılışta bir kez doğrula
  • Doğrulamayı sınıra koy (Zod), içeride tiplere güven
  • React.FC ve enum alışkanlıklarını gözden geçir

TypeScript bir kanıtlayıcı değil, bir iletişim aracı. Ne bildiğini kodun içine yazmanı sağlıyor — ama ona yalan söylersen sessizce inanıyor. İşin zor kısmı tip yazmak değil, yalan söylemediğinden emin olmak.

Bu yazıyı faydalı bulduysan paylaşabilirsin.

Furkan Özay

Yazılım geliştirici ve eğitmen; Next.js, React ve altyapı üzerine yazıyor.

czay.dev

Bunu da oku

Yorumlar