TypeScript template literal types, JavaScript'teki şablon dizgelerinin (`merhaba ${ad}`) sözdizimini tip düzeyinde kullanarak yeni string literal tipleri üreten tiplerdir. type Selam = `merhaba ${"dunya"}` tanımı "merhaba dunya" tipini verir; araya bir union koyarsanız olası tüm birleşimler üretilir. Uppercase, Lowercase, Capitalize ve Uncapitalize ise bu tiplerde harf dönüşümü yapan yerleşik tiplerdir.

Bu yazıda template literal tiplerinin union'larla çarpımını, nesne anahtarlarından olay adı üretmeyi, infer ile string parçalamayı ve dört intrinsic string tipini TypeScript 6.0 ile denenmiş örneklerle anlatıyoruz. Çalışma anındaki şablon dizgeleri için JavaScript template literal yazımıza, konuların kısa özeti için TypeScript ileri seviye özeti yazımıza bakabilirsiniz.

Temel kullanım

Temel kullanım

Template literal tipi, string literal tipleri üzerine kurulur. Somut literal tiplerle kullanıldığında içerikleri birleştirerek yeni bir literal tip üretir:

typescript
type Dunya = "dunya";
type Selam = `merhaba ${Dunya}`; // "merhaba dunya"

const s1: Selam = "merhaba dunya"; // geçerli
const s2: Selam = "merhaba ay";
// TS2322: Type '"merhaba ay"' is not assignable to type '"merhaba dunya"'.

Union'larla çarpım

Yer tutucuya bir union koyarsanız sonuç, her üye için bir string içeren bir union olur. Birden fazla yer tutucu varsa union'lar çapraz çarpılır:

typescript
type EpostaId = "hosgeldin_eposta" | "eposta_baslik";
type AltbilgiId = "altbilgi_baslik" | "altbilgi_kapanis";

type TumId = `${EpostaId | AltbilgiId}_id`;
// "hosgeldin_eposta_id" | "eposta_baslik_id" | "altbilgi_baslik_id" | "altbilgi_kapanis_id"

type Dil = "tr" | "en" | "de";
type MesajId = `${Dil}_${TumId}`;
// 3 x 4 = 12 üyeli bir union: "tr_hosgeldin_eposta_id" | ... | "de_altbilgi_kapanis_id"

Union boyutu çarpımla hızla büyür. Handbook, büyük string union'ları için bunları önceden (kod üreterek) oluşturmayı önerir; template literal çarpımı küçük kümeler için uygundur. Çok büyük bir union üretmeye çalışırsanız derleyici durur:

typescript
type Rakam = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
type Bes = `${Rakam}${Rakam}${Rakam}${Rakam}${Rakam}`;
// TS2590: Expression produces a union type that is too complex to represent.

Primitive yer tutucular: ${number} ve ${string}

Yer tutucuya bir literal yerine string, number gibi primitive bir tip de koyabilirsiniz. Bu durumda tip bir küme değil bir desen tanımlar. TypeScript 4.3 sürüm notları bu desenlerin birbiriyle ilişkilendirilebildiğini de gösterir:

typescript
type Piksel = `${number}px`;

const g1: Piksel = "120px";  // geçerli
const g2: Piksel = "1.5px";  // geçerli
const g3: Piksel = "otomatikpx";
// TS2322: Type '"otomatikpx"' is not assignable to type '`${number}px`'.

type ApiYolu = `/api/${string}`;
const y: ApiYolu = "/api/kullanicilar"; // geçerli

Nesne anahtarlarından olay adı üretmek

Handbook'taki "izlenen nesne" örneği template literal tiplerinin gücünü iyi gösterir. Bir nesneye on() metodu ekleyen bir fonksiyon düşünün; olay adı her zaman bir özellik adı artı Degisti ekinden oluşsun. keyof ile string kesişimini bir template literal içine koyarak yalnızca geçerli olay adlarını kabul edebilirsiniz:

typescript
type OlayKaynagi<T> = {
  on(olayAdi: `${string & keyof T}Degisti`, geriCagir: (yeni: unknown) => void): void;
};

declare function izle<T>(nesne: T): T & OlayKaynagi<T>;

const kisi = izle({ ad: "Deniz", soyad: "Kaya", yas: 26 });

kisi.on("adDegisti", () => {});   // geçerli

kisi.on("ad", () => {});
// TS2345: Argument of type '"ad"' is not assignable to parameter of type '"adDegisti" | "soyadDegisti" | "yasDegisti"'.

string & keyof T kesişimi gereklidir, çünkü keyof T sayı ve sembol anahtarları da içerebilir. Kesişim, yalnızca string anahtarları template'e sokar.

Template literal ile çıkarım

Yukarıdaki örnekte geri çağırma fonksiyonunun parametresi unknown. Metodu generic yaparsanız TypeScript, "adDegisti" argümanını desene eşleyip Anahtar için "ad" tipini çıkarır; ardından indexed access ile özelliğin tipini bulur:

typescript
type OlayKaynagi<T> = {
  on<Anahtar extends string & keyof T>(
    olayAdi: `${Anahtar}Degisti`,
    geriCagir: (yeniDeger: T[Anahtar]) => void,
  ): void;
};

declare function izle<T>(nesne: T): T & OlayKaynagi<T>;

const kisi = izle({ ad: "Deniz", yas: 26 });

kisi.on("adDegisti", (yeniAd) => {
  // yeniAd: string
  console.log(yeniAd.toUpperCase());
});

kisi.on("yasDegisti", (yeniYas) => {
  // yeniYas: number
  if (yeniYas < 0) console.warn("negatif yaş");
});

infer ile string parçalamak

Conditional types içinde template literal deseni kullanıp parçaları infer ile yakalayabilirsiniz. Özyineleme ile birleştirince bir rota dizgesindeki parametre adlarını çıkarmak mümkündür:

typescript
type IlkKelime<S> = S extends `${infer Ilk} ${string}` ? Ilk : S;
type K = IlkKelime<"merhaba dunya">; // "merhaba"

type RotaParam<S extends string> =
  S extends `${string}:${infer P}/${infer Kalan}`
    ? P | RotaParam<`/${Kalan}`>
    : S extends `${string}:${infer P}`
      ? P
      : never;

type P = RotaParam<"/kullanici/:id/siparis/:siparisId">; // "id" | "siparisId"

Intrinsic string tipleri

Intrinsic string tipleri: Uppercase, Lowercase, Capitalize, Uncapitalize

TypeScript string literal tiplerinde harf dönüşümü için dört yerleşik tip sunar. Bunlar performans nedeniyle derleyicinin içine gömülüdür ve TypeScript'le gelen .d.ts dosyalarında tanımları bulunmaz.

TipNe yaparÖrnekSonuç
Uppercase<S>Tüm karakterleri büyük harfe çevirirUppercase<"merhaba">"MERHABA"
Lowercase<S>Tüm karakterleri küçük harfe çevirirLowercase<"MERHABA">"merhaba"
Capitalize<S>Yalnızca ilk karakteri büyütürCapitalize<"merhaba dunya">"Merhaba dunya"
Uncapitalize<S>Yalnızca ilk karakteri küçültürUncapitalize<"MERHABA">"mERHABA"
typescript
type Selam = "Merhaba, dunya";
type Bagiran = Uppercase<Selam>;   // "MERHABA, DUNYA"
type Sessiz = Lowercase<Selam>;    // "merhaba, dunya"

type OnbellekAnahtari<S extends string> = `ID-${Uppercase<S>}`;
type Ana = OnbellekAnahtari<"benim_uygulamam">; // "ID-BENIM_UYGULAMAM"

type OlayAdi<T extends string> = `on${Capitalize<T>}`;
type Olaylar = OlayAdi<"click" | "change">; // "onChange" | "onClick"
typescript
type T1 = Uppercase<"istanbul">; // "ISTANBUL"
type T2 = Uppercase<"ılık">;     // "ILIK"

// Çalışma anında da aynı davranış (yerel ayar verilmezse):
console.log("istanbul".toUpperCase());          // ISTANBUL
console.log("istanbul".toLocaleUpperCase("tr")); // İSTANBUL

Mapped types ile birlikte

Template literal tiplerinin en sık kullanıldığı yer, mapped types içindeki as cümleciğidir. Bir nesnenin her alanı için setAd, setYas gibi metot adları üretmek:

typescript
type Setterlar<T> = {
  [K in keyof T as `set${Capitalize<string & K>}`]: (deger: T[K]) => void;
};

type Profil = { ad: string; yas: number };

type ProfilSetter = Setterlar<Profil>;
// { setAd: (deger: string) => void; setYas: (deger: number) => void; }

Ne zaman template literal tip kullanmalısınız?

Ne zaman template literal tip kullanmalısınız? Kullanın, Kullanın, Kullanın, Dikkatli olun

Template literal tipleri, string değerlerin belirli bir kurala uyduğu ve bu kuralın başka bir tipten türetilebildiği durumlarda en çok işe yarar. Kural elle yazılmış birkaç sabitten ibaretse düz bir string literal union çoğu zaman daha okunaklıdır. Aşağıdaki liste karar vermenize yardımcı olabilir:

  • Kullanın: olay adları, getter/setter adları, önbellek anahtarları gibi bir nesnenin anahtarlarından türetilen isimler.
  • Kullanın: `${number}px` ya da `/api/${string}` gibi sabit bir ön ek veya son ek taşıyan değer desenleri.
  • Kullanın: rota ya da biçim dizgelerinden infer ile parametre adlarını çıkarmak.
  • Dikkatli olun: binlerce üyeli kombinasyonlar. Bu durumda listeyi kod üreterek önceden oluşturmak daha uygundur.
  • Gerek yok: zaten elinizde olan küçük ve sabit bir değer listesi için "acik" | "koyu" gibi düz bir union yeterlidir.

Unutmayın ki bu tipler yalnızca derleyiciye yardım eder. Dışarıdan gelen veriyi (form girdisi, API yanıtı) çalışma anında ayrıca doğrulamanız gerekir; derleyici bu değerlerin içeriğini göremez.

Sık yapılan hatalar

  • keyof T'yi doğrudan template'e koymak: `${keyof T}Degisti` yazmak, symbol anahtarlar string'e çevrilemeyeceği için hata verir. string & keyof T kullanın.
  • Büyük union üretmek: çapraz çarpım hızla büyür ve TS2590 hatasına ya da yavaş derlemeye yol açar.
  • Türkçe karakterlerde locale beklemek: intrinsic tipler locale duyarlı değildir.
  • Desen tiplerini literal sanmak: `${number}px` sonsuz sayıda değeri kabul eden bir desendir; switch ile tüm durumları tek tek ele alamazsınız.

Sık Sorulan Sorular

Template literal types hangi sürümde geldi?

TypeScript 4.1 ile geldi; aynı sürümde Uppercase gibi intrinsic tipler ve mapped type'larda as cümleciği de eklendi. TypeScript 6.0 ile ek bir ayar gerekmez.

Çalışma anında bir string'i bu tiple doğrulayabilir miyim?

Hayır. Template literal tipleri yalnızca derleme zamanında vardır. Dışarıdan gelen bir değeri (örneğin bir API yanıtını) doğrulamak için çalışma anında kontrol yazmanız, sonra tipi type guard ile daraltmanız gerekir. Metin desenleri için düzenli ifadeler işinize yarayabilir.

Capitalize Türkçe "i" harfini doğru çevirir mi?

Hayır. Capitalize<"istanbul"> sonucu "Istanbul" olur, çünkü dönüşüm JavaScript'in yerel ayarsız toUpperCase() fonksiyonuyla yapılır.

${string} ile string arasındaki fark nedir?

Tek başına `${string}` her string'i kabul eder. Fark, sabit parçalarla birleştiğinde ortaya çıkar: `/api/${string}` yalnızca /api/ ile başlayan string'leri kabul eder.

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 · Mapped types · Conditional types · Literal tipler ve as const · keyof ve typeof · TypeScript ileri seviye özeti