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

TypeScript'te Temel ve Özel Tipler: any, unknown, never ve void

Mert Özen 3 Kas 2026 7 dk 1 görüntülenme
TypeScript'te Temel ve Özel Tipler: any, unknown, never ve void

string, number, boolean, bigint ve symbol'ün yanında null ve undefined'ın strict moddaki yerini, any'nin neden tehlikeli olduğunu, unknown'ın onu nasıl güvenli hâle getirdiğini ve void ile never'ı görüyoruz.

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

Önce Tahmin Et

İki fonksiyon, aynı gövde, tek fark parametrenin type'ı:

function trimSafe(input: unknown) {
  return input.trim();
}

function trimLoose(input: any) {
  return input.trim();
}

console.log(trimLoose(42));

unknown ve any ilk bakışta aynı şeyi söylüyor gibi: "burada her şey olabilir". Peki bu iki fonksiyondan hangisi derlenir? Derlenen fonksiyonu son satırdaki gibi bir sayıyla çağırdığında ne olur? Cevap, iki type'ı da tanıdıktan sonra gelecek.

Primitive Tipler: string, number, boolean

JavaScript serilerinden bildiğin primitive değerlerin her birinin TypeScript'te aynı adlı bir type'ı var. En sık kullandıkların string, number ve boolean. Hepsi küçük harfle yazılır ve JavaScript'teki typeof sonuçlarıyla birebir örtüşür.

let price = 19.99;

price = 20;
price = NaN;
price = Infinity;
// Hata: Type 'string' is not assignable to type 'number'.
price = "20";

Burada dikkat çeken bir şey var: TypeScript'te tam sayı ve ondalıklı sayı ayrımı yok. 20 da 19.99 da number. NaN ve Infinity da öyle; JavaScript'te nasıl sayıysa TypeScript'te de sayılar. Yani number type'ı sana "bu değer geçerli bir fiyattır" garantisi vermez, sadece "bu bir sayıdır" der. string için de aynısı geçerli: tek tırnak, çift tırnak ya da template literal fark etmez. boolean ise yalnızca iki değer alır: true ve false.

bigint ve symbol

Daha az kullanılan iki primitive daha var. bigint, number'ın güvenle tutamadığı çok büyük tam sayılar içindir. Farkı şu satırda görebilirsin:

console.log(9007199254740993n, 9007199254740993);
// 9007199254740993n 9007199254740992

function addFee(amount: bigint, fee: number) {
  // Hata: Operator '+' cannot be applied to types 'bigint' and 'number'.
  return amount + fee;
}

İlk değer sondaki n sayesinde bir bigint ve olduğu gibi yazdırılıyor. İkincisi number; JavaScript'in güvenli tam sayı sınırını aştığı için son hanesi sessizce değişiyor. Fonksiyondaki hata da JavaScript'in kuralını yansıtıyor: bigint ile number'ı karıştırırsan çalışma zamanında TypeError alırsın. TypeScript bu hatayı kod çalışmadan önce gösteriyor. İkisini toplamak istiyorsan birini açıkça dönüştürürsün: amount + BigInt(fee).

symbol ise her seferinde benzersiz bir değer üretir. const id = Symbol("id") yazdığında TypeScript id'nin type'ını unique symbol olarak gösterir: bu değere eşit başka hiçbir symbol yok. let ile yazarsan type sadece symbol olur. Symbol'lerle temel seride pek karşılaşmayacağız; burada var olduklarını bilmen yeterli.

null, undefined ve strictNullChecks

JavaScript'te "değer yok" demenin iki yolu var: null ve undefined. TypeScript'te ikisi de kendi type'ına sahip ve strict modda başka hiçbir type'ın içine kendiliğinden girmezler:

let nickname: string = "ada";
// Hata: Type 'null' is not assignable to type 'string'.
nickname = null;

let middleName: string | null = null;
middleName = "Lovelace";

Bir değişken gerçekten boş kalabiliyorsa bunu type'ında açıkça söylersin: string | null, "string ya da null" demek. Bu kuralı sağlayan ayarın adı strictNullChecks ve strict'in bir parçası. Asıl faydasını şu tür kodda görürsün:

const users = ["Ada", "Alan"];
const found = users.find((user) => user === "Grace");

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

if (found !== undefined) {
  console.log(found.toUpperCase());
}

console.log(found?.toUpperCase());
// undefined

find aradığını bulamazsa undefined döndürür. TypeScript bunu bilir ve found'un type'ını string | undefined olarak çıkarır. Doğrudan toUpperCase çağırmana izin vermez, çünkü çalışma zamanında bu satır "Cannot read properties of undefined" hatasıyla patlayabilir. if ile kontrol ettiğinde ya da optional chaining (?.) kullandığında hata kaybolur. if bloğunun içinde TypeScript'in found'u artık string olarak görmesine narrowing denir; ona ayrı bir yazı ayıracağız.

strictNullChecks kapalı olsaydı null ve undefined her type'a atanabilirdi, found'un type'ı düz string görünürdü ve hata ancak canlı ortamda ortaya çıkardı. JavaScript hatalarının belki de en yaygını olan bu durum, strict modun en değerli kazancıdır.

any: Tip Denetiminin Kapandığı Yer

any, TypeScript'e "bu değeri denetleme" demenin yoludur. any type'ındaki bir değerle her şeyi yapabilirsin: istediğin property'yi okursun, istediğin method'u çağırırsın, istediğin yere atarsın. TypeScript hiçbirine itiraz etmez. Tahmindeki trimLoose bu yüzden derlenir ve çalışınca şu hatayla patlar:

TypeError: input.trim is not a function

any'yi kendin yazmasan bile karşına çıkabilir. En bilinen kaynağı JSON.parse:

const data = JSON.parse('{"age": 36}');
// const data: any

const age = data.agee;
// const age: any

console.log(age.toFixed(1));

JSON.parse'ın dönüş tipi any, çünkü TypeScript string'in içinden ne çıkacağını bilemez. agee yazım hatasını kimse yakalamaz; üstelik age de any olur. any bulaşıcıdır: ondan türettiğin her değer de denetimsiz kalır. Son satır çalışma zamanında "Cannot read properties of undefined (reading 'toFixed')" hatası verir. Tip sisteminde bir delik açtın ve o delik kodun içinde yayılmaya devam ediyor.

strict'in içindeki noImplicitAny ayarı, en azından bu deliğin farkında olmadan açılmasını engeller. Bir önceki yazıda gördüğümüz "Parameter 'value' implicitly has an 'any' type." hatası bu ayardan gelir: TypeScript bir type'ı çıkaramadığında sessizce any'ye düşmek yerine senden annotation ister. any'yi bilerek yazmak hâlâ mümkün, ama artık bu bir karar oluyor, kaza değil.

unknown: Güvenli any

unknown da "burada her şey olabilir" der, ama sonucu tam tersidir: değeri kullanmadan önce ne olduğunu kanıtlamanı ister. Tahmindeki trimSafe bu yüzden derlenmez:

error TS18046: 'input' is of type 'unknown'.

Kanıtlamanın en basit yolu JavaScript'ten bildiğin typeof:

function trimSafe(input: unknown) {
  if (typeof input === "string") {
    return input.trim();
  }
  return "";
}

console.log(`[${trimSafe("  Ada  ")}]`); // [Ada]
console.log(`[${trimSafe(42)}]`); // []

if bloğunun içinde TypeScript input'un bir string olduğunu bilir ve trim çağrısına izin verir. Dışarıdan gelen her değeri böyle düşün: bir API cevabı, bir dosyanın içeriği, kullanıcının yazdığı bir şey. Bunları any yerine unknown olarak karşılarsan TypeScript seni kontrol yazmaya zorlar. JSON.parse örneğinde de const data: unknown = JSON.parse(...) yazarak bu deliği kapatabilirsin.

Bir benzetme: any, kimlik sormadan herkesi içeri alan bir kapı görevlisi. unknown ise herkesi kapıda bekletip kimliğine bakan bir görevli. İkisinin önünden de herkes geçebilir, ama yalnızca biri içeride ne olduğunu bilir.

void ve never

Bu iki type daha çok fonksiyonların dönüş tiplerinde karşına çıkar. void, "bu fonksiyon kullanılacak bir değer döndürmez" demektir:

function logMessage(message: string): void {
  console.log(message);
}

const result = logMessage("Saved");
// const result: void

never ise "bu noktaya hiçbir zaman ulaşılmaz" demektir. Her zaman hata fırlatan bir fonksiyon hiçbir zaman normal şekilde geri dönmez; dönüş tipi never'dır:

function fail(message: string): never {
  throw new Error(message);
}

// Hata: Type '1' is not assignable to type 'never'.
let impossible: never = 1;

never hiçbir değeri kabul etmeyen, boş bir type. İlk bakışta işe yaramaz gibi görünür, ama narrowing yazısında bir switch'in bütün ihtimalleri kapsayıp kapsamadığını denetlemek için onu kullanacağız. İnce bir nokta: fail gibi bir fonksiyon declaration'ında annotation'ı yazmazsan TypeScript dönüş tipini never değil void olarak çıkarır. Niyetin "bu fonksiyon asla dönmez" ise bunu açıkça yaz.

Sık Düşülen Tuzaklar

Birincisi, büyük harfle String, Number, Boolean yazmak. Bunlar primitive type'lar değil, JavaScript'teki wrapper object'lerin type'ları. TypeScript bunu fark ettiğinde mesajında açıkça söyler: "'string' is a primitive, but 'String' is a wrapper object. Prefer using 'string' when possible." Her zaman küçük harfli olanları kullan.

İkincisi, bir hatayı susturmak için any'ye sarılmak. Hata kaybolur ama hatanın sebebi yerinde durur, üstelik any etrafındaki kodu da denetimsiz bırakır. Type'ını gerçekten bilmediğin bir değer varsa unknown kullan ve kontrolünü yaz.

Üçüncüsü, null ve undefined'ı aynı şey sanmak. TypeScript ikisini ayrı type'lar olarak görür: string | null olarak tanımladığın bir değişkene undefined atayamazsın. Projede hangisini "boş" anlamında kullandığına karar ver ve tutarlı ol.

Küçük Bir Deneme

★☆☆ Derlenir mi? let total: number = undefined; Hata verirse, bu değişkenin boş başlayabilmesi için type'ını nasıl değiştirirdin?

★★☆ const value: unknown = "hello"; yazdıktan sonra value.length satırını ekle. Hatayı oku, sonra bir typeof kontrolüyle düzelt.

★★☆ Tahmin et: function fail(message: string) { throw new Error(message); } fonksiyonunun dönüş tipine annotation yazmazsan editör hangi type'ı gösterir? Aynı fonksiyonu const fail = (message: string) => { throw new Error(message); }; şeklinde arrow function olarak yazınca ne değişir?

★★★ 10n + 5 ifadesinin neden derlenmediğini açıkla ve iki farklı şekilde düzelt: bir kez sonucu bigint, bir kez de number olacak şekilde.

Tek tek değerlerin type'larını artık biliyoruz. Ama gerçek kodda değerler nadiren yalnız gezer; çoğu zaman bir listenin içindedirler. Bir sonraki yazıda array'lerin type'larını, değiştirilemeyen readonly array'leri ve her konumu ayrı bir type taşıyan tuple'ları göreceğiz.