TypeScript'te dekoratör, bir sınıfın veya sınıf üyesinin önüne @ifade biçiminde yazılan ve çalışma zamanında o tanım hakkında bilgi alarak onu gözlemleyen, değiştiren ya da yerine yenisini koyan bir fonksiyondur. TypeScript 5.0'dan itibaren ECMAScript dekoratör önerisine uyan "standart" dekoratörler hiçbir derleyici bayrağı olmadan çalışır. Eski experimentalDecorators bayrağı ise önerinin çok daha eski bir sürümünü uygular; imzası, desteklediği yerler ve çıktısı farklıdır.

Bu yazıda iki sistemi yan yana koyuyor, standart dekoratörlerle metot, alan ve sınıf dekoratörü yazıyor, uygulanma sırasını ve addInitializer kullanımını gösteriyoruz. Bütün örnekler TypeScript 6.0.3 ile derlenip Node.js 24 ile çalıştırıldı. Dekoratörler sınıflar üzerinde çalıştığı için önce TypeScript class yazısını okumanızı öneririz; serinin tamamı TypeScript öğrenme rehberi sayfasında.

İki ayrı dekoratör sistemi

İki ayrı dekoratör sistemi: Bayrak gerekir mi, Metot dekoratörü imzası, Parametre dekoratörü, emitDecoratorMetadata

TypeScript 5.0 sürüm notları --experimentalDecorators bayrağının öngörülebilir gelecekte kalacağını, ancak bayrak olmadan yazılan dekoratörlerin artık standart kurallara göre denetlenip derleneceğini söyler. İki sistemin kuralları o kadar farklıdır ki eski dekoratör fonksiyonlarının yeni sistemde çalışması beklenmez:

ÖzellikStandart (TS 5.0+)experimentalDecorators
Bayrak gerekir miHayırEvet
Metot dekoratörü imzası(deger, context)(target, propertyKey, descriptor)
Parametre dekoratörüYokVar
emitDecoratorMetadataKullanılamazKullanılabilir
Metadatacontext.metadata (TS 5.2)reflect-metadata kütüphanesi ile
export ile sıraÖnce veya sonra yazılabilirÖnce yazılır

İlk standart dekoratör: metot loglama

Standart bir metot dekoratörü iki argüman alır: süslenen metodun kendisi ve bir context nesnesi. Bir fonksiyon döndürürseniz o fonksiyon metodun yerine geçer. Aşağıdaki örnek TypeScript 5.0 sürüm notlarındaki iyi tiplenmiş loggedMethod kalıbını temel alıyor:

typescript
function logla<This, Args extends any[], Return>(
  hedef: (this: This, ...args: Args) => Return,
  context: ClassMethodDecoratorContext<This, (this: This, ...args: Args) => Return>,
) {
  const ad = String(context.name);

  function yeniMetot(this: This, ...args: Args): Return {
    console.log(`> ${ad} çağrıldı:`, args);
    const sonuc = hedef.call(this, ...args);
    console.log(`< ${ad} döndü:`, sonuc);
    return sonuc;
  }

  return yeniMetot;
}

class Sepet {
  private toplam = 0;

  @logla
  ekle(fiyat: number, adet: number) {
    this.toplam += fiyat * adet;
    return this.toplam;
  }
}

new Sepet().ekle(250, 2);
// > ekle çağrıldı: [ 250, 2 ]
// < ekle döndü: 500

This, Args ve Return tip parametreleri orijinal metodun this tipini, parametrelerini ve dönüş tipini korur. Generic yazımın temelleri için generics yazısına bakabilirsiniz. Sürüm notları basit durumlarda any ile başlamanın da mümkün olduğunu, ancak dekoratörlerin yazıldığından çok daha fazla kullanıldığı için iyi tiplenmiş sürümün genellikle tercih edilmesi gerektiğini belirtir.

Dekoratör fabrikası, uygulanma sırası ve addInitializer

Dekoratör fabrikası, uygulanma sırası ve addInitializer

Dekoratöre ayar geçirmek için dekoratör döndüren bir fonksiyon (dekoratör fabrikası) yazarsınız. Birden fazla dekoratör üst üste geldiğinde önce ifadeler yukarıdan aşağıya değerlendirilir, sonra dekoratörler aşağıdan yukarıya uygulanır. Context nesnesindeki addInitializer ise sınıf örneği oluşturulurken çalışacak bir fonksiyon kaydetmenizi sağlar:

typescript
function bagla(_hedef: Function, context: ClassMethodDecoratorContext) {
  const ad = context.name;
  context.addInitializer(function (this: any) {
    this[ad] = this[ad].bind(this);
  });
}

function onEk(etiket: string) {
  console.log(`onEk("${etiket}") fabrikası çalıştı`);
  return function (hedef: Function, context: ClassMethodDecoratorContext) {
    console.log(`onEk("${etiket}") dekoratörü uygulandı: ${String(context.name)}`);
  };
}

class Kullanici {
  ad: string;
  constructor(ad: string) {
    this.ad = ad;
  }

  @bagla
  @onEk("A")
  @onEk("B")
  selamla() {
    return `Merhaba, ${this.ad}`;
  }
}

const k = new Kullanici("Deniz");
const ayri = k.selamla;   // metodu nesneden kopardık
console.log(ayri());

// onEk("A") fabrikası çalıştı
// onEk("B") fabrikası çalıştı
// onEk("B") dekoratörü uygulandı: selamla
// onEk("A") dekoratörü uygulandı: selamla
// Merhaba, Deniz

bagla hiçbir şey döndürmediği için metodu değiştirmez; yalnızca constructor başında metodu örneğe bağlar. Bu sayede metot nesneden koparılıp çağrıldığında da this kaybolmaz. this bağlamının neden kaybolduğunu JavaScript this yazısında anlattık.

Alan ve sınıf dekoratörleri

Standart sistemde alan (field) dekoratörünün ilk argümanı undefined olur, çünkü dekoratör çalıştığında alanın henüz değeri yoktur. Bir fonksiyon döndürürseniz bu fonksiyon alanın ilk değerini alır ve yerine geçecek değeri döndürür:

typescript
function buyukHarf(_deger: undefined, context: ClassFieldDecoratorContext<unknown, string>) {
  return function (ilkDeger: string) {
    return ilkDeger.toUpperCase();
  };
}

class Sehir {
  @buyukHarf
  kod = "ist";
}

console.log(new Sehir().kod); // IST

Sınıf dekoratörü sınıfın kendisini ve bir ClassDecoratorContext alır. Aşağıdaki örnek, süslenen sınıfların adlarını bir listeye kaydeder:

typescript
const kayit: string[] = [];

function kaydet<T extends abstract new (...args: any) => any>(
  deger: T,
  context: ClassDecoratorContext<T>,
) {
  kayit.push(String(context.name));
}

@kaydet
class FaturaServisi {}

@kaydet
class KargoServisi {}

console.log(kayit); // [ 'FaturaServisi', 'KargoServisi' ]

TypeScript 4.9 ile gelen accessor anahtar kelimesiyle tanımlanan auto-accessor alanlar ve getter/setter'lar da süslenebilir. TypeScript 5.2 ise her context nesnesine bir metadata nesnesi ekledi; sınıftaki bütün dekoratörler aynı nesneyi paylaşır ve sonuç sınıf üzerinde Symbol.metadata ile okunur. Denediğimiz Node.js 24.4 sürümünde Symbol.metadata tanımlı değildi (typeof Symbol.metadata sonucu undefined), bu yüzden bu özelliği kullanmadan önce çalışma ortamınızı kontrol edin.

Derleme hedefi önemli: target esnext dikkat

Dekoratörler henüz her JavaScript motorunda doğrudan çalışmıyor. --target es2022 veya TypeScript 6.0'ın varsayılan hedefiyle (es2025) derlediğimizde tsc dekoratörleri yardımcı fonksiyonlarla çalışan koda çevirdi ve çıktı Node.js 24'te sorunsuz çalıştı. --target esnext ile derlediğimizde ise @logla satırı çıktıda olduğu gibi kaldı ve Node.js 24.4 dosyayı çalıştırırken SyntaxError: Invalid or unexpected token verdi.

experimentalDecorators: eski sistem

Eski sistemde metot dekoratörü üç argüman alır: statik üyelerde sınıfın constructor'ı, örnek üyelerinde prototype; üyenin adı; ve property descriptor. Handbook'taki @enumerable örneğinin uyarlanmış hâli:

typescript
// tsc --experimentalDecorators
function sayilabilir(deger: boolean) {
  return function (hedef: any, ad: string, tanimlayici: PropertyDescriptor) {
    tanimlayici.enumerable = deger;
  };
}

class Selamlayici {
  @sayilabilir(true)
  selamla() {
    return "Merhaba";
  }
}

console.log(Object.keys(Selamlayici.prototype)); // [ 'selamla' ]

Aynı dosyayı bayrak olmadan derlediğimizde derleyici, standart sistemin bu dekoratörü iki argümanla çağıracağını söyleyerek hata verdi. Tersi de geçerli: standart logla dekoratörü --experimentalDecorators açıkken derlenmedi.

typescript
// Bayraksız derleme:
// TS1241: Unable to resolve signature of method decorator when called as an expression.
//   The runtime will invoke the decorator with 2 arguments, but the decorator expects 3.

function zorunlu(hedef: any, ad: string, sira: number) {}

class Rapor {
  yazdir(@zorunlu ayrintili: boolean) {
    // Bayraksız: TS1206: Decorators are not valid here.
    return ayrintili;
  }
}

Parametre dekoratörleri yalnızca eski sistemde vardır. emitDecoratorMetadata da yalnızca eski sistemle çalışır; tek başına verildiğinde tsc 6.0 şu hatayı verdi: TS5052: Option 'emitDecoratorMetadata' cannot be specified without specifying option 'experimentalDecorators'. Handbook'taki eski dekoratör örnekleri --target ES5 ile gösterilir; TypeScript 6.0'da target: es5 kullanımdan kaldırıldı (deprecated), bu yüzden o komutları olduğu gibi kopyalamayın.

Sık yapılan hatalar

Sık yapılan hatalar: İnternetteki eski örnekleri (üç argümanlı imza) bayrak açmadan kullanmak: sonuç..., Standart sistemde parametre dekoratörü yazmaya çalışmak, Dekoratörün...
  • İnternetteki eski örnekleri (üç argümanlı imza) bayrak açmadan kullanmak: sonuç TS1241 olur.
  • Standart sistemde parametre dekoratörü yazmaya çalışmak: TS1206.
  • Dekoratörün süslenen metodun tipini değiştireceğini sanmak. Handbook'taki sınıf dekoratörü örneğinde eklenen yeni özellik tip sistemine yansımaz.
  • --target esnext ile derlenmiş dekoratörlü kodu dekoratörleri desteklemeyen bir ortamda çalıştırmak.
  • Değiştirici dekoratörden dönen fonksiyonda this ve argümanları orijinal metoda aktarmayı unutmak.

Sık Sorulan Sorular

Yeni projede hangi dekoratör sistemini kullanmalıyım?

Bağımlı olduğunuz bir kütüphane eski sistemi şart koşmuyorsa standart dekoratörleri kullanın; bayrak gerektirmezler ve ECMAScript önerisini izlerler. TypeScript 5.0 sürüm notları yeni sistemin parametre dekoratörlerini ve emitDecoratorMetadata'yı desteklemediğini açıkça belirtir.

Dekoratörler JavaScript standardının parçası mı?

experimentalDecorators sayfası dekoratörlerin JavaScript belirtimine henüz tam olarak alınmadığını söyler. TypeScript 5.0'daki standart dekoratörler TC39 önerisinin güncel hâline dayanır. Çalışma ortamınızın doğrudan desteği yoksa tsc çıktıyı yardımcı kodla dönüştürür.

export ile birlikte dekoratörü nereye yazmalıyım?

Standart sistemde dekoratörü export anahtar kelimesinden önce veya sonra yazabilirsiniz; ama ikisini aynı sınıfta karıştırmak hata verir.

Dekoratör sınıfın tipini değiştirebilir mi?

Dekoratör çalışma zamanında sınıfı veya üyeyi değiştirebilir, ancak TypeScript'in gördüğü tip değişmez. Yeni eklenen bir özelliği tip düzeyinde de kullanmak istiyorsanız onu sınıf tanımına ayrıca yazmanız gerekir.

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 · TypeScript class ve erişim belirleyiciler · TypeScript generics · target ve lib ayarları · JavaScript this · JavaScript class