TypeScript'te literal tip, genel bir tip yerine tek bir değeri ifade eden tiptir: "GET", 42 ya da true gibi. Tek başına pek işe yaramaz, ama union ile birleştiğinde ("left" | "right" | "center") bir parametrenin alabileceği değerleri sınırlamanın en kolay yolu olur. as const ise bir ifadenin tamamını literal tiplerle çıkarır: Değerler genişletilmez, nesne özellikleri readonly, diziler readonly tuple olur.

Bu yazıda derleyicinin literal tipleri ne zaman koruduğunu ve ne zaman genişlettiğini (widening), sık görülen "string is not assignable to type 'GET'" hatasının nedenini, as const'un etkilerini ve sınırlarını örneklerle anlatıyoruz. Örnekler TypeScript 6.0.3 ile tsc --strict --target es2022 --moduleDetection force seçenekleriyle derlendi; hata kodları ve mesajları bu derleyicinin gerçek çıktısıdır. Union tiplerine yabancıysanız önce union ve intersection yazısına bakmanızı öneririz.

let ve const neden farklı tip çıkarır?

let ve const neden farklı tip çıkarır?

JavaScript'te let ile tanımlanan değişkenin değeri değişebilir, const ile tanımlananın değişemez. TypeScript bu farkı tip çıkarımına yansıtır. let değişkeni ileride herhangi bir metni tutabileceği için tipi string olur; const değişkeni yalnızca tek bir değeri temsil edebileceği için tipi o değerin literal tipidir:

typescript
let degisen = "Merhaba";  // tipi: string
const sabit = "Merhaba";  // tipi: "Merhaba"

const a: "Merhaba" = sabit; // geçerli
const b: "Merhaba" = degisen;
// TS2322: Type 'string' is not assignable to type '"Merhaba"'.

Değişkenin tipini let x: "hello" diye kendiniz yazarsanız o değişkene başka bir metin atayamazsınız. Tek değer alabilen bir değişken pek kullanışlı değildir; literal tiplerin gücü birleştirildiklerinde ortaya çıkar.

Literal union: izin verilen değerleri listelemek

Literal tipleri union ile birleştirerek bir fonksiyonun yalnızca belirli değerleri kabul etmesini sağlarsınız. Yanlış yazılmış bir değer derleme sırasında yakalanır:

typescript
function hizala(metin: string, yon: "left" | "right" | "center") {
  return `${yon}: ${metin}`;
}

hizala("Merhaba", "left"); // geçerli
hizala("Selam", "centre");
// TS2345: Argument of type '"centre"' is not assignable to parameter of type '"center" | "left" | "right"'.

Hata mesajındaki union'ın sırası sizin yazdığınız sıradan farklı olabilir; bu yalnızca gösterimle ilgilidir. Sayısal literal'ler de aynı şekilde çalışır ve literal'ler sıradan tiplerle de birleştirilebilir:

typescript
function kiyasla(a: string, b: string): -1 | 0 | 1 {
  return a === b ? 0 : a > b ? 1 : -1;
}

interface Secenekler {
  genislik: number;
}
function ayarla(x: Secenekler | "auto") {
  return x === "auto" ? "otomatik" : `${x.genislik}px`;
}

console.log(kiyasla("a", "b"), kiyasla("b", "a")); // -1 1
console.log(ayarla("auto"), ayarla({ genislik: 100 })); // otomatik 100px

Boolean da aslında bir literal union'dır: Handbook'a göre boolean tipi true | false union'ının başka bir adıdır. Bu yüzden true ve false da ayrı ayrı tip olarak kullanılabilir ve discriminated union'larda ayırt edici alan olabilir.

Nesnelerde literal çıkarımı: sık görülen hata

Nesnelerde literal çıkarımı: sık görülen hata

Bir nesneyi const ile tanımlasanız bile özellikleri değiştirilebilir. TypeScript bu yüzden nesne özelliklerinin tipini genişletir: { sayac: 0 } için sayac'ın tipi 0 değil number olur. Aynı kural metinler için de geçerlidir ve şu tanıdık hataya yol açar:

typescript
declare function istekGonder(url: string, method: "GET" | "POST"): void;

const istek = { url: "https://example.com", method: "GET" };
istekGonder(istek.url, istek.method);
// TS2345: Argument of type 'string' is not assignable to parameter of type '"GET" | "POST"'.

istek.method'un tipi string'dir, çünkü nesne oluşturulduktan sonra ve fonksiyon çağrılmadan önce biri ona "GUESS" gibi bir değer atayabilir. Handbook bu durum için üç çözüm gösterir:

typescript
declare function istekGonder(url: string, method: "GET" | "POST"): void;

// 1. Özelliğin tipini sabitlemek: method hep "GET" kalacak
const i1 = { url: "https://example.com", method: "GET" as "GET" };
istekGonder(i1.url, i1.method);

// 2. Çağrı yerinde bilgi vermek: "Bu değerin GET olduğunu biliyorum"
const i2 = { url: "https://example.com", method: "GET" };
istekGonder(i2.url, i2.method as "GET");

// 3. Tüm nesneyi literal tiplere çevirmek
const i3 = { url: "https://example.com", method: "GET" } as const;
istekGonder(i3.url, i3.method);

Üçüncü seçeneğin yanında bir dördüncüsü de vardır: Nesneye baştan doğru tipi vermek, örneğin method: "GET" | "POST" alanı olan bir interface ile. İkinci yöntem derleyiciye bir söz verir ve kontrol etmez; tip assertion'ların riskleri için type assertion yazısına bakabilirsiniz.

as const ne yapar?

TypeScript 3.4 ile gelen const assertion, bir literal ifadenin sonuna as const eklenerek yazılır. Sürüm notlarına göre üç etkisi vardır: İfadedeki literal tipler genişletilmez, nesne literal'lerinin özellikleri readonly olur ve dizi literal'leri readonly tuple'a dönüşür:

typescript
let x = "merhaba" as const;   // tipi: "merhaba"
let y = [10, 20] as const;     // tipi: readonly [10, 20]
let z = { metin: "selam" } as const; // tipi: { readonly metin: "selam" }

y.push(30);
// TS2339: Property 'push' does not exist on type 'readonly [10, 20]'.
z.metin = "x";
// TS2540: Cannot assign to 'metin' because it is a read-only property.

as const özellikle tip yazmadan sabit veri tanımlarken işe yarar. Aşağıdaki dizide hiçbir tip tanımı yok, ama kind alanları literal kaldığı için döngüdeki kontrol her nesneyi doğru şekle daraltır:

typescript
function sekiller() {
  return [
    { kind: "daire", yaricap: 100 },
    { kind: "kare", kenar: 50 },
  ] as const;
}

for (const s of sekiller()) {
  if (s.kind === "daire") {
    console.log("Daire yarıçapı", s.yaricap);
  } else {
    console.log("Kare kenarı", s.kenar);
  }
}

Çıktı sırasıyla Daire yarıçapı 100 ve Kare kenarı 50 olur.

Enum benzeri sabit nesneler

Sürüm notları as const'un düz JavaScript'te enum benzeri kalıplar kurmak için de kullanılabileceğini belirtir. Değerlerin union'ını da aynı nesneden türetebilirsiniz; buradaki typeof ve keyof operatörlerini keyof, typeof ve indexed access yazısında ayrıntılı anlatıyoruz:

typescript
const Renk = {
  kirmizi: "RED",
  mavi: "BLUE",
  yesil: "GREEN",
} as const;

type Renk = (typeof Renk)[keyof typeof Renk]; // "RED" | "BLUE" | "GREEN"

function boya(r: Renk) {
  return `renk=${r}`;
}

console.log(boya(Renk.mavi)); // renk=BLUE
console.log(Object.values(Renk).join(",")); // RED,BLUE,GREEN

Bu kalıp ile TypeScript enum'ı arasındaki farkları enum ve alternatifleri yazısında karşılaştırıyoruz.

Spread argümanlarında as const

TypeScript dizilerin değişmez olduğunu varsaymaz. [8, 5] dizisinin tipi "iki sayı" değil, "sıfır ya da daha fazla sayı" anlamına gelen number[] olur. Bu yüzden tam iki argüman bekleyen bir fonksiyona spread ile verilemez:

typescript
const argumanlar = [8, 5];
const aci = Math.atan2(...argumanlar);
// TS2556: A spread argument must either have a tuple type or be passed to a rest parameter.

Handbook'un önerdiği en kısa çözüm as const'tur; dizi iki elemanlı bir tuple olarak çıkarılır:

typescript
const argumanlar = [8, 5] as const; // readonly [8, 5]
const aci = Math.atan2(...argumanlar);
console.log(aci.toFixed(4)); // 1.0122

readonly sonuçlar ve sınırları

as const ile elde ettiğiniz readonly dizi, değiştirilebilir bir dizi bekleyen parametreye verilemez. Bu iyi bir korumadır, çünkü fonksiyon diziyi değiştirebilirdi:

typescript
function siralaVeYaz(liste: string[]) {
  return liste.sort().join(",");
}

const adlar = ["b", "a"] as const;
siralaVeYaz(adlar);
// TS2345: Argument of type 'readonly ["b", "a"]' is not assignable to parameter of type 'string[]'.

Fonksiyon diziyi değiştirmeyecekse parametreyi readonly string[] olarak yazmak doğru çözümdür; böylece hem normal hem readonly diziler kabul edilir. Readonly dizi ve tuple tiplerinin ayrıntıları tuple ve readonly dizi yazısında.

as const'un iki sınırı vardır. Birincisi, yalnızca doğrudan literal ifadelere uygulanabilir; bir hesaplamanın sonucuna uygulanamaz:

typescript
let a = (Math.random() < 0.5 ? 0 : 1) as const;
// TS1355: A 'const' assertion can only be applied to references to enum members, or string, number, boolean, array, or object literals.

let b = Math.random() < 0.5 ? (0 as const) : (1 as const); // geçerli: 0 | 1

İkincisi, as const değeri derinlemesine dondurmaz. Nesnenin bir özelliği dışarıda tanımlanmış ve değiştirilebilir bir diziye işaret ediyorsa o dizi değiştirilebilir kalır. Ayrıca tüm bunlar yalnızca derleme zamanıdır; çalışma zamanında Object.freeze gibi bir etki yoktur:

typescript
const dizi = [1, 2, 3];
const kayit = { ad: "foo", icerik: dizi } as const;

kayit.icerik.push(4); // geçerli: icerik'in tipi number[]
console.log(kayit.icerik); // [ 1, 2, 3, 4 ]

const tip parametreleri (TypeScript 5.0)

Kendi yazdığınız generic bir fonksiyonu çağıranların her seferinde as const yazmasını istemiyorsanız, TypeScript 5.0'dan beri tip parametresine const değiştiricisi ekleyebilirsiniz. Sürüm notlarının uyarısı önemlidir: Kısıt readonly string[] gibi değişmez bir tip olmalıdır, aksi hâlde çıkarım kısıta geri düşer:

typescript
function adlariAl<const T extends readonly string[]>(adlar: T): T {
  return adlar;
}

const sonuc = adlariAl(["Ali", "Ayşe"]); // tipi: readonly ["Ali", "Ayşe"]
const ilk: "Ali" = sonuc[0]; // geçerli
console.log(ilk); // Ali

Bu değiştirici yalnızca çağrının içinde yazılan literal ifadeleri etkiler; önceden bir değişkende tutulan diziyi geçirdiğinizde fark etmez. Generic'lerin geri kalanı için TypeScript generics yazısına bakın.

Özet tablo

YazımÇıkarılan tipNot
let s = "a"stringDeğişebileceği için genişletilir
const s = "a""a"Tek değer, literal kalır
const o = { m: "GET" }{ m: string }Özellikler değişebilir
{ m: "GET" } as const{ readonly m: "GET" }Literal ve readonly
[1, 2]number[]Uzunluk bilinmez
[1, 2] as constreadonly [1, 2]Readonly tuple
"GET" as "GET""GET"Tek bir değerin tipini sabitler

Sık yapılan hatalar

Sık yapılan hatalar: Nesne özelliğinin literal kalacağını sanmak, as const'u derin dondurma sanmak, Readonly diziyi mutable parametreye vermek, Hesaplanmış değere as const...
  • Nesne özelliğinin literal kalacağını sanmak: const nesnenin özellikleri genişletilir; TS2345 hatasının en yaygın nedeni budur.
  • as const'u derin dondurma sanmak: Yalnızca tip düzeyinde readonly yapar ve dışarıdan referans verilen dizileri kapsamaz.
  • Readonly diziyi mutable parametreye vermek: Fonksiyon diziyi değiştirmiyorsa parametreyi readonly T[] yapın.
  • Hesaplanmış değere as const uygulamak: TS1355 alırsınız; as const'u literal'lerin kendisine uygulayın.
  • Tip assertion ile yanlış söz vermek: method as "GET" derleyiciyi ikna eder ama değeri kontrol etmez.

Sık Sorulan Sorular

as const ile Object.freeze aynı şey mi?

Hayır. as const yalnızca derleyiciye bilgi verir ve JavaScript çıktısında iz bırakmaz. Object.freeze ise çalışma zamanında nesneyi dondurur. İkisini birlikte kullanabilirsiniz.

Literal union mı enum mı?

Literal union'lar ek kod üretmez ve JSON'dan gelen düz metinlerle doğrudan uyumludur. Enum'un artı ve eksileri için enum yazısına bakın.

satisfies ile as const birlikte kullanılır mı?

Evet; as const literal tipleri korurken satisfies değerin bir tipe uyduğunu kontrol eder. Bu kombinasyonu satisfies operatörü yazısında anlatıyoruz.

Template literal tiplerle ilişkisi nedir?

Template literal tipler, string literal tiplerden yeni string literal tipler üretir. Konu için template literal types yazısına bakabilirsiniz.

Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.

İlgili içerikler: TypeScript öğrenme rehberi · Union ve intersection tipleri · Tuple ve readonly dizi · Enum ve alternatifleri · satisfies operatörü · keyof, typeof ve indexed access · TypeScript ileri seviye yazısı