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.
type User = { id: number; name: string };
function greet(user: User) {
return `Merhaba ${user.name}`;
}Bu fonksiyon derlendiğinde geriye şu kalıyor:
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.
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.
Uyarı: 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
const raw = localStorage.getItem("settings");
const settings = JSON.parse(raw!); // any
settings.theme.colors.primary; // derleyici sessizJSON.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>.
İpucu: 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:
const settings: unknown = JSON.parse(raw ?? "{}");
// settings.theme → derleme hatası, önce doğrulaman gerekiyorDelik 3: ! operatörü
Ünlem işareti "bu null ya da undefined değil" demek — yine bir iddia.
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:
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:
const arr: string[] = [];
const first = arr[0];
console.log(first.length); // çalışma anında: Cannot read properties of undefinedTypeScript 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:
Örnek: tsconfig.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.
Açıklama: 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| undefinedalırexactOptionalPropertyTypes—{ a?: string }alanına açıkçaundefinedatamayı engellernoImplicitOverride— sınıftaoverrideyazmayı zorunlu kılarnoPropertyAccessFromIndexSignature— 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
const dbUrl = process.env.DATABASE_URL; // string | undefined
const client = new Client({ url: dbUrl! }); // yine iddiaOrtam 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:
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:
Örnek: Sınırda doğrulama
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:
// 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:
// Ç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];Uyarı: 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
Özet: Kontrol listesi
- Tipler çalışma anında yok; güvence yalnız kendi kodunun içinde
asve!birer iddia; dış veride kullanılırsa doğrulama yapılmamış demektirJSON.parseveres.json()anydöner —unknownolarak al, daraltmaya zorlastrictdizi indekslemesini yakalamıyor;noUncheckedIndexedAccessayrı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.FCveenumalış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.
İnteraktif Yol Haritası & Araç