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

TypeScript'te Array ve Tuple Tipleri: readonly, Optional Element ve Named Tuple

Mert Özen 3 Kas 2026 8 dk 1 görüntülenme
TypeScript'te Array ve Tuple Tipleri: readonly, Optional Element ve Named Tuple

string[] ve generic Array yazımını, readonly array'leri, sabit uzunluklu tuple'ları, optional ve rest element'leri, named tuple'ları ve noUncheckedIndexedAccess ayarının index okumalarını nasıl değiştirdiğini görüyoruz.

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

Önce Tahmin Et

Bir noktanın x ve y koordinatlarını iki element'lik bir yapıda tutuyoruz. Annotation'daki köşeli parantezler bu yazının konusu; şimdilik "tam iki number" diye oku:

const point: [number, number] = [10, 20];

point.push(30);
const [x, y, z] = point;

Bu kodda bir ya da daha fazla satır hata verebilir. Hangisi? İki number için tanımlanmış bir yapıya üçüncü bir sayı eklemek mi, yoksa ondan üç değer okumaya çalışmak mı? Cevap ikiye bölünmüş durumda: yarısını tuple'ları anlattığımız bölümde, diğer yarısını readonly tuple'larda vereceğiz.

Array Tipi: string[] ve Array<string>

JavaScript serilerinde array'lerle çok çalıştık. TypeScript'te bir array'in type'ını yazmanın iki yolu var ve ikisi tamamen aynı anlama gelir:

const tags: string[] = ["typescript", "javascript"];
const ids: Array<number> = [1, 2, 3];

// Hata: Argument of type 'number' is not assignable to parameter of type 'string'.
tags.push(42);

string[], "string'lerden oluşan bir array" demek. Array<number> ise aynı şeyin generic syntax ile yazılmış hâli; < ve > işaretlerinin arasındaki type, array'in element'lerinin type'ı. Generic'leri Orta seride ayrıntılı göreceğiz. Şimdilik ikisinden birini seç ve projede tutarlı kullan; bu yazıda kısa olan string[] yazımını kullanacağız.

Array'in type'ı belli olduktan sonra TypeScript her işlemi buna göre denetler. tags.push(42) bu yüzden reddedilir. Annotation yazmasan da sonuç değişmez; bir önceki yazılardan hatırlarsın, ["typescript", "javascript"] değerinden TypeScript zaten string[] çıkarır. Annotation'a asıl ihtiyacın, boş bir array ile başladığında ya da bir fonksiyon parametresinde olur.

readonly Array: Okunur Ama Değişmez

Bazen bir array'in hiç değiştirilmemesini istersin. Bunun için type'ın başına readonly yazarsın:

const weekdays: readonly string[] = ["Mon", "Tue", "Wed"];

// Hata: Property 'push' does not exist on type 'readonly string[]'.
weekdays.push("Thu");
// Hata: Index signature in type 'readonly string[]' only permits reading.
weekdays[0] = "Sun";

readonly string[] type'ında push, pop, sort gibi array'i yerinde değiştiren method'lar yoktur, index ile atama da yapılamaz. map, filter, slice gibi yeni bir array döndüren method'lar ise yerinde durur. Aynı type'ı ReadonlyArray<string> olarak da yazabilirsin.

readonly array'lerin en faydalı olduğu yer fonksiyon parametreleri. Parametreyi readonly yazdığında çağıran tarafa bir söz vermiş olursun: "senin array'ine dokunmayacağım."

function printAll(items: readonly string[]) {
  for (const item of items) {
    console.log(item);
  }
}

function sortInPlace(items: string[]) {
  items.sort();
}

const languages = ["typescript", "go"];
const frozen: readonly string[] = ["rust", "zig"];

printAll(languages);
printAll(frozen);
// Hata: The type 'readonly string[]' is 'readonly' and cannot be assigned to the mutable type 'string[]'.
sortInPlace(frozen);

Normal bir array readonly bekleyen bir yere rahatça verilebilir; değiştirmeyeceğini söyleyen birine değiştirilebilir bir şey vermek risk taşımaz. Tersi ise hata: değiştirmemesi gereken bir array'i, onu yerinde sıralayacak bir fonksiyona veremezsin. Bir şeyi unutma: readonly yalnızca derleme zamanında vardır. Çalışma zamanında array'i gerçekten dondurmak istiyorsan JavaScript serilerinden bildiğin Object.freeze'e ihtiyacın var.

Tuple: Her Konumun Kendi Tipi

Array'de bütün element'ler aynı type'ı paylaşır ve sayıları belli değildir. Bazen ise tam tersini istersin: uzunluğu sabit ve her konumda farklı bir type olan bir yapı. Buna tuple denir:

const entry: [string, number] = ["Ada", 36];

entry[0].toUpperCase();
entry[1].toFixed(1);
// Hata: Tuple type '[string, number]' of length '2' has no element at index '2'.
const extra = entry[2];
// Hata: iki konumda da type uyuşmuyor (TS2322).
const swapped: [string, number] = [36, "Ada"];

TypeScript, entry[0]'ın bir string, entry[1]'in bir number olduğunu bilir ve üçüncü bir konumun olmadığını da bilir. Tahmindeki const [x, y, z] = point satırı da tam bu yüzden hata verir:

error TS2493: Tuple type '[number, number]' of length '2' has no element at index '2'.

Tuple'lar özellikle bir fonksiyondan birden fazla değer döndürmek için kullanışlıdır:

function getMinMax(values: number[]): [number, number] {
  return [Math.min(...values), Math.max(...values)];
}

const [low, high] = getMinMax([4, 8, 15, 16, 23, 42]);
console.log(low, high); // 4 42

Burada dönüş tipini yazmak zorundayız, çünkü TypeScript bir array literal'ından kendiliğinden tuple çıkarmaz. const pair = ["Ada", 36] yazarsan type'ı [string, number] değil, (string | number)[] olur: herhangi bir uzunlukta, her konumunda string de number da olabilen bir array. Tuple istiyorsan bunu annotation ile söylersin.

Optional ve Rest Element'ler

Tuple'ın uzunluğu her zaman tek bir sayı olmak zorunda değil. Bir element'in sonuna ? koyarsan o element isteğe bağlı olur:

const release: [number, number, number?] = [7, 0];
const patchRelease: [number, number, number?] = [7, 0, 2];

console.log(release.length, patchRelease.length); // 2 3

const patch = release[2];
// const patch: number | undefined

Optional bir element'in type'ı otomatik olarak undefined'ı da içerir; kullanmadan önce kontrol etmen gerekir. length property'sinin type'ı da artık tek bir sayı değil, 2 | 3.

Rest element ile ise "başta sabit konumlar, sonra istediğin kadar şu type" diyebilirsin:

const scoreRow: [string, ...number[]] = ["Ada", 90, 85, 70];
const nameOnly: [string, ...number[]] = ["Ada"];
// Hata: Type 'number' is not assignable to type 'string'.
const noName: [string, ...number[]] = [90, 85];

İlk element her zaman bir string, ardından sıfır ya da daha fazla number gelir. Bir tablo satırı ya da bir komutun adı ve argümanları gibi yapılar için birebir.

Named Tuple: Konumlara İsim Vermek

[number, number] type'ı neyin ne olduğunu söylemez. Hangisi minimum, hangisi maksimum? TypeScript 4.0 ile gelen named tuple'lar bu soruyu çözer:

function getRange(values: number[]): [min: number, max: number] {
  return [Math.min(...values), Math.max(...values)];
}

const range = getRange([3, 9, 1]);
console.log(range[0], range[1]); // 1 9

Buradaki isimler yalnızca okunabilirlik içindir; type'ı değiştirmez ve çalışma zamanında hiçbir karşılığı yoktur. range.min diye bir property oluşmaz, değerlere yine index ile ya da destructuring ile ulaşırsın. Ama editörde getRange'in üzerine geldiğinde dönüş tipini [min: number, max: number] olarak görürsün ve fonksiyonu kullanan kişi neyin nerede olduğunu tahmin etmek zorunda kalmaz. İsimler optional element'lerle de çalışır: [major: number, minor: number, patch?: number].

readonly Tuple ve Tahminin Diğer Yarısı

Tahmindeki ilk soruya dönelim: point.push(30) satırı hata verir mi? Şaşırtıcı ama vermez. Tuple'lar TypeScript'te özel bir array türüdür ve normal bir tuple array'in bütün method'larını taşır, push dahil. Kod çalıştığında point'in içinde gerçekten üç sayı olur, type ise hâlâ iki sayı olduğunu söyler. Bu, tip sisteminin bilinen küçük deliklerinden biri.

Deliği kapatmanın yolu tuple'ı readonly yapmak:

const point: readonly [number, number] = [10, 20];

// Hata: Property 'push' does not exist on type 'readonly [number, number]'.
point.push(30);

Değeri sonradan değişmeyecek tuple'ları, özellikle koordinat ya da aralık gibi küçük değer gruplarını, readonly yazmak iyi bir alışkanlık. Hem bu delikten korunursun hem de okuyan kişiye "bu yapı sabit" mesajı verirsin.

noUncheckedIndexedAccess'e Kısa Giriş

Şu kod strict modda sorunsuz derlenir:

const names = ["Ada", "Grace"];

console.log(names[5].toUpperCase());

Ama çalıştığında "Cannot read properties of undefined (reading 'toUpperCase')" hatası verir. Sebep şu: TypeScript string[] type'ındaki bir array'in her index'inden bir string döneceğini varsayar. Array'in kaç element'i olduğunu derleme zamanında bilemez.

tsc --init'in ürettiği tsconfig'de açık olan noUncheckedIndexedAccess ayarı bu varsayımı değiştirir. Ayar açıkken names[5]'in type'ı string | undefined olur ve aynı satır şu hatayı verir:

error TS2532: Object is possibly 'undefined'.

Bu ayar strict'in parçası değil; açtığında index ile yaptığın her okumayı kontrol etmen gerekir. Bunun bir bedeli var, ama tam da yukarıdaki türden hataları yakalar. Sabit uzunluklu tuple'lar bundan etkilenmez: entry[0] hâlâ düz bir string, çünkü TypeScript o konumun var olduğunu biliyor. for...of döngüsü de etkilenmez. Bu ayarın ayrıntılarına tsconfig yazısında döneceğiz.

Sık Düşülen Tuzaklar

Birincisi parantez meselesi. "String ya da number taşıyan bir array" yazmak isterken string | number[] yazarsan aslında "ya bir string ya da bir number array'i" demiş olursun. Doğrusu (string | number)[]. Emin değilsen Array<string | number> yaz, orada karışıklık olmaz.

İkincisi, tuple beklerken array almak. Annotation yazmadan oluşturduğun ["Ada", 36] bir tuple değildir. Bir fonksiyondan tuple döndürüyorsan dönüş tipini yaz.

Üçüncüsü, readonly'nin çalışma zamanında bir şey yaptığını sanmak. Sadece TypeScript'in sana izin verdiği işlemleri kısıtlar. Kodun bir yerinde any varsa ya da veri TypeScript'in görmediği bir yerden geliyorsa array yine değiştirilebilir.

Küçük Bir Deneme

★☆☆ Derlenir mi? const scores: number[] = [90, 85]; scores.push("100"); Peki scores'un type'ını readonly number[] yapınca hata mesajı nasıl değişir?

★★☆ Tahmin et: const user = ["Ada", 36]; için TypeScript hangi type'ı çıkarır? user[0].toUpperCase() satırı derlenir mi? Annotation ekleyerek bu değişkeni bir tuple'a çevir ve aynı satırı tekrar dene.

★★☆ Bir HTTP isteğini temsil eden named tuple yaz: ilk element method adı (string), ikincisi URL (string), üçüncüsü isteğe bağlı bir timeout (number). İki ve üç element'li iki örnek değer oluştur.

★★★ getMinMax fonksiyonunu boş bir array ile çağır ve sonucu yazdır. Type ne diyor, çalışma zamanında ne çıkıyor? Bu farkı kapatmak için dönüş tipini nasıl değiştirirdin?

Array ve tuple'larla sıralı veriyi tipledik. Ama JavaScript'te verinin büyük kısmı sırayla değil, isimle tutulur: bir kullanıcının adı, e-postası, yaşı. Bir sonraki yazıda object tiplerini, optional ve readonly property'leri, index signature'ları ve TypeScript'in fazladan yazdığın property'leri neden bazen yakalayıp bazen yakalamadığını göreceğiz.