Decorator, bir sınıfa ya da bir sınıf üyesine @name ile eklediğiniz bir fonksiyondur. Orijinal metodu (ya da sınıfı, ya da alanı) ve onu tarif eden bir context nesnesini alır ve yerine geçecek bir değer döndürebilir. TypeScript standart decorator'ları hiçbir derleyici bayrağı olmadan destekler.
@logged, sınıf tanımlandığında bir kez çalışır ve add metodunu döndürdüğü sarmalayıcıyla değiştirir. Her çağrı bu sarmalayıcıdan geçer. Generic parametreler metodun this, argüman ve dönüş tiplerini korur; bu yüzden add hâlâ iki sayı alır ve bir sayı döndürür.
Decorator'lar Nasıl Derlenir
Standart decorator'lar JavaScript için bir TC39 önerisinden gelir ve TypeScript onları TypeScript 5.0'dan beri implement eder. Öneri henüz JavaScript standardının parçası değildir ve Node 24 @ sözdizimini ayrıştırmaz; bu yüzden hedef ES2022 olduğunda (bu sayfalarda olduğu gibi) derleyici dekore edilmiş her sınıfı yardımcı fonksiyonları (__esDecorate ve __runInitializers, dosyanın en üstüne yazılır) çağıran düz JavaScript'e dönüştürür. Çıktı, ES2022'nin çalıştığı her yerde çalışır.
Bunun bir sonucu var: decorator içeren bir dosya, yalnızca tipleri kaldırıp @ işaretini yerinde bırakan Node'un yerleşik tip ayıklamasıyla (node file.ts) çalışamaz. Node SyntaxError: Invalid or unexpected token ile durur. Önce tsc ya da bir bundler ile derleyin.
Decorator Türleri ve İmzaları
Her standart decorator (value, context) => replacement | void biçimindedir. value değerinin ne olduğu ve ne döndürebileceğiniz, neyin dekore edildiğine bağlıdır:
| Dekore ettiği | value | Context tipi | Dönüş |
|---|---|---|---|
| sınıf | sınıfın kendisi | ClassDecoratorContext | yerine geçecek bir sınıf ya da hiçbir şey |
| metot | metodun kendisi | ClassMethodDecoratorContext | yerine geçecek bir metot |
| getter / setter | getter ya da setter | ClassGetterDecoratorContext / ClassSetterDecoratorContext | yerine geçecek bir getter ya da setter |
| alan | undefined | ClassFieldDecoratorContext | başlangıç değerini dönüştüren bir fonksiyon |
accessor alanı | { get, set } | ClassAccessorDecoratorContext | { get?, set?, init? } |
Her context nesnesinde kind, name ve addInitializer vardır. Bir sınıf üyesinin context'inde ayrıca static, private ve üyeyi bir örnekten okumak için bir access nesnesi bulunur. Decorator'lar statik ve #private üyelerde de çalışır.
Decorator Factory'leri
Seçenek geçirmek için bir decorator döndüren bir fonksiyon yazın ve onu @ konumunda çağırın. Buna decorator factory denir:
@retry(3) önce retry fonksiyonunu çağırır; asıl decorator onun döndürdüğü fonksiyondur. Birden fazla decorator üst üste binebilir: @a @b method() içinde önce b uygulanır, sonucu da a sarar.
Sınıf Decorator'ları ve addInitializer
Bir sınıf decorator'ı sınıfın kendisini alır. Onun yerine geçecek bir alt sınıf döndürebilir ya da hiçbir şey döndürmeyip sınıfı bir yere kaydedebilir. context.addInitializer, belirli bir anda çalışacak kodu kaydeder: bir sınıf decorator'ı için sınıf tamamen tanımlandıktan hemen sonra, bir metot decorator'ı için her örnek oluşturulduğunda.
Sınıf decorator'ı sınıfın üstüne (ya da export sonrasına) yazılır. @bound olmasaydı loose() çağrısı hata fırlatırdı, çünkü this değeri undefined olurdu.
Alan ve Accessor Decorator'ları
Bir alan decorator'ı sonraki atamaları göremez ve araya giremez: value değeri undefined'dır ve döndürebileceği tek şey alanın başlangıç değerini dönüştüren bir fonksiyondur. Okumalara ve yazmalara araya girmek için alanı accessor anahtar kelimesiyle bildirin; bu, onu private bir depolamayla desteklenen bir getter ve setter çiftine dönüştürür. Sonra onu dekore edin:
accessor aynı önerinin parçasıdır. Bir #private alan üzerinde gerçek bir getter ve setter üretir; p.price = -5 atamasının decorator'ın set fonksiyonundan geçmesinin nedeni budur.
Standart Model ve Eski experimentalDecorators
TypeScript 5.0'dan önce TypeScript'in sahip olduğu tek decorator'lar, experimentalDecorators ile açılan önerinin erken bir sürümüydü. Bu bayrak hâlâ vardır ve derleyiciyi farklı imzalara ve semantiğe sahip eski modele geçirir:
| Standart (bayraksız) | Eski (experimentalDecorators) | |
|---|---|---|
| İmza | (value, context) | (target, propertyKey, descriptor) |
| Bir metodu nasıl değiştirir | yeni bir fonksiyon döndürür | descriptor.value değerini değiştirir |
| Parametre decorator'ları | desteklenmez (TS1206) | desteklenir |
emitDecoratorMetadata | desteklenmez | desteklenir (reflect-metadata ile çalışma zamanı tip bilgisi) |
accessor decorator'ları ({ get, set, init }), addInitializer | evet | hayır |
| Temeli | TC39 önerisi | önerinin eski bir taslağı |
Bir model için yazılmış bir decorator diğerinde tip kontrolünden geçmez. İşte bayrağı olmayan bir projede eski tarzda yazılmış bir decorator:
index.ts(11,5): error 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.
Çözüm ya onu bu sayfadaki ilk örnekteki gibi standart (value, context) biçiminde yeniden yazmak ya da tüm proje için eski modeli açmaktır:
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}
Angular, NestJS ve TypeORM, araçlarının ürettiği yapılandırmalarda hâlâ experimentalDecorators ayarlar ve dokümantasyonları da bunu ister. NestJS ve TypeORM ayrıca emitDecoratorMetadata seçeneğine ihtiyaç duyar, çünkü tipleri çalışma zamanında okurlar: NestJS constructor parametrelerini enjekte etmek için, TypeORM ise özellikleri sütunlara eşlemek için:
// Legacy model: needs experimentalDecorators (and emitDecoratorMetadata for DI).
@Injectable()
class UsersService {
constructor(@Inject(DB) private db: Database) {}
}
Bu framework'lerden birini kullanıyorsanız decorator'ları eski yöntemle yazın ve onun dokümantasyonunu izleyin. Böyle bir framework olmayan yeni kodlarda standart decorator'ları kullanın.
Decorator'lar Ne Zaman Kullanılmalı
Decorator'lar, aksi halde birçok metotta tekrarlanacak kesişen davranışlara uyar: loglama, süre ölçme, önbellekleme, yeniden denemeler, erişim kontrolleri, doğrulama ve sınıfları bir container'a ya da router'a kaydetme. Aynı zamanda kontrol akışını da gizlerler, çünkü okuyanın metodun ne yaptığını bilmeden önce @retry ifadesinin ne yaptığına bakması gerekir. Bir iki kullanım için düz bir higher-order fonksiyon (const fetchData = retry(3, rawFetch)) daha basittir ve sınıfların dışında da çalışır.
Sıkça Sorulan Sorular
TypeScript'te decorator nedir?
Decorator, bir sınıfa ya da sınıf üyesine @name sözdizimiyle uygulanan bir fonksiyondur. Dekore edilen şeyi ve bir context nesnesini alır ve bir yerine geçecek değer döndürebilir: sarmalanmış bir metot, yeni bir sınıf ya da bir alanın başlangıç değerini dönüştüren bir fonksiyon. Yaygın kullanımları loglama, doğrulama, önbellekleme ve sınıfları kaydetmektir.
TypeScript'te decorator kullanmak için experimentalDecorators gerekli mi?
Hayır. TypeScript 5.0'dan beri standart (TC39) decorator'lar hiçbir bayrak olmadan çalışır. experimentalDecorators derleyiciyi Angular ve NestJS gibi framework'lerin üzerine kurulduğu eski decorator modeline geçirir. İkisi farklı fonksiyon imzaları kullanır ve birbirinin yerine kullanılamaz.
Standart decorator'lar ile experimentalDecorators arasındaki fark nedir?
Standart decorator'lar (value, context) alır ve yerine geçecek bir değer döndürür. Eski decorator'lar (target, propertyKey, descriptor) alır ve property descriptor'ı değiştirir. Parametre decorator'larını ve emitDecoratorMetadata seçeneğini yalnızca eski model destekler; { get, set, init } döndüren accessor decorator'ları ve context.addInitializer ise yalnızca standart modelde vardır.
TypeScript parametre decorator'larını destekler mi?
Yalnızca experimentalDecorators açıkken. Standart modda bir parametre üzerindeki decorator TS1206: Decorators are not valid here hatasıdır, çünkü TC39 önerisi parametre decorator'larını içermez. Bu yüzden constructor parametrelerini dekore eden dependency injection framework'lerinin eski bayrağa ihtiyacı vardır.
Birden fazla decorator hangi sırayla uygulanır?
Decorator ifadeleri yukarıdan aşağıya değerlendirilir ama aşağıdan yukarıya uygulanır: @a @b method() içinde metodu önce b sarar, sonucu da a sarar. Bu yüzden metot çağrıldığında en dışta a çalışır.