TypeScript’in Sihirli Özelliği: Decorator
TypeScript’in Sihirli Özelliği: Decorator
Daha önce Java, C# gibi dillerle uğraştıysanız sınıf, fonksiyon veya parametrelerin önüne yazılan ve kodun çalışmasını etkileyebilen, köşeli parantez benzeri şeyler kullanılan garip yazımlı kelimelerle karşılaşmışsınızdır. Peki bunların ne olduğunu veya TypeScript ile yapılarınızı nasıl sarmalayacağınızı yahut onlara bazı anlamlar yükleyebileceğinizi hiç merak ettiniz mi? Gelin bunlara birlikte bakalım.
Decorator Nedir?
Decorator, sınıflarla ve içindeki methotlarla, parametrelerle, property’lerle, erişimcilerle etkileşime girebilen bir tür özel fonksiyonlardır. Bu bahsettiğimiz şeyleri değiştirmemize, içine yeni değerler eklememize, değerlerine göre farklı aksiyonlar almamıza ve de bunlara meta data olarak adlandırdığımız bilgi yükleme olanaklarını bize sağlar.
JavaScript’te Dekoratörler henüz stage 2 seviyesinde olup TypeScript’te ise deneysel olarak kullanılmaktadır. TS 5.0 ile stage 3 seviyesine ilerlemiş ve kullanımında değişiklikler yapılmıştır. Bu yazı stage 2 özelliklerini anlatmaktadır.
Decorator’lar ile annotations ve meta-programming yapılması mümkün hale gelmiştir. Meta data’nın ne olduğunu ve bununla neler yapılabileceğini “C# Attribute’lara Derinlemesine Bakış” adlı yazımdan detaylıca öğrenebilirsiniz.
Bunlar dışında nesne üzerindeki verileri doğrulayabilir, fonksiyonunuz çağrıldığında ondan önce çalışacak bir sarmayalıcı oluşturabilirsiniz.
Nasıl kullanılır?
Decorator’ler hala dile tam olarak eklenmediği
için TypeScript Compailer’a decorator kullanacağımızı belirtmemiz gerekiyor. Derleme komutunuza --target ES5 --experimentalDecorators
ekleyerek ya da tsconfig.json
dosyanıza aşağıdaki key’i ekleyerek kullanıma
hazır hale gelebilirsiniz.
"compilerOptions": {
"target": "ES5",
"experimentalDecorators": true
}
Dekorator’ler @decoratorİsmi
şeklinde çağrılır. Run time anında decoratorİsmi
fonksiyonuna ifade ettiği alanın bilgileri
verilerek çağrılır. İlgili alanların önüne ya da bir üst satırına yazılarak kullanılabilirler ve içlerine
parametre alabilirler.
Tabloİsmi('users')
class Kullanıcı {
ı
email: string;
öneticiMi
set birimNumarası() {}
örü
fly( 10) meters: number ) {} üyükOlmalıdır(
} ı
Çoklu Decorator kullanımı
Aynı alan için bieden fazla dekorator kullanımı mümkündür.
Aralarına boşluk bırakarak tek satırda yazılabilir.
function kaydet(){}
veya alt alta kullanabilirsiniz.
function kaydet(){
console.log('kaydet fonksiyonu çalişti');
}
Değerlendirilme ve Çağrılma Sıraları
Sınıf dekoratörü new
anahtar kelimesi kullanıldığında diğerleri ise bulunduğu
method tetiklendiği anda ilk önce değerlendirilir ardından ise çağrılır. Değerlendirme işlemi Decorator
Factory kısmında bahsedeceğimiz fonksiyonların gövdesinden ibarettir. İlk önce alana tanımlı olan Factory
gövdeleri çalıştırılır ardından ise Factory’nin geri döndüğü ana dekoratör.
Üyeler arasındaki sıralama
Dekoratörlerin birbirleri arasındaki çağırılma sırası şu şekildedir:
1- Parametre dekoratörleri ardından method, erişimci ve son olarak property dekoratörleri sınıftan üretilen instance için çalıştırılır.
2- Parametre dekoratörleri ardından method, erişimci ve son olarak property dekoratörleri sınıfın statik üyeleri için çalıştırılır.
3- Kurucu fonksiyonların parametre dekoratörleri çalıştırılır.
4- Sınıfın dekoratörü çalıştırılır.
Yani daha akılda kalıcı olacak şekilde söylersek
dekoratörler içeriden dışarıya doğru sıralanır. parameter -> method -> accessor -> property -> constructor -> class
Aynı üyedeki birden fazla dekoratörün sıralaması
class Kullanıcı{
function kaydet(){
console.log('kaydet fonksiyonu çalıştı');
}
}
let k = new Kullanıcı();
k.kaydet();
Bunların çalıştırma sırası alana en yakın
olandan en uzak olana doğrudur. Matematikdeki birleşim özelliği bunun en güzel açıklamasıdır a(b(c(kaydet)))
veya (a o b o c)(kaydet)
fakat değerlendirme sırası ise en
dışarıdan en içeriye doğrudur.
Nasıl Kendi Decorator’ımızı Oluştururuz?
Her dekoratör ait olduğu üyeyi ve ona ait diğer özellikleri içine alır böylece o üyeyi manipüle edebiliriz.
Class Decorator
Tek parametresi vardır o da sınıfın constructor fonksiyonudur. Burada sınıfın içine yeni method veya property ekleyebilir yada olanları ezebiliriz.
// toString fonksiyonunu ezen ve JSON stringfy yapan dekoratör
function toString<T extends Consturctor>(BaseClass: T) {
return class extends BaseClass {
toString() {
return JSON.stringify(this);
}
};
}
class C {
public foo = "foo";
public num = 24;
}
Dekoratörün geri dönderdiği değer sınıfla değiştirilecektir.
Method Decorator
Üç parametresi vardır bunlar sırasıyla: hedef aldığı fonksiyon, fonksiyonun ismi ve method için Property Descriptor tipindeki bit tanımlayıcı.
Methodu yeniden atamak, method çağrıldığında log’lama ve benzeri işlemler yapmak için kullanılabilir.
Descriptor içinde şu key’ler bulunur:
- value
- writable
- enumerable
- configurable
function logger(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const original = descriptor.value;
descriptor.value = function (...args) {
console.log('params: ', ...args);
const result = original.call(this, ...args);
console.log('result: ', result);
return result;
}
}
class C {
add(x: number, y:number ) {
return x + y;
}
}
const c = new C();
c.add(1, 2);
// -> params: 1, 2
// -> result: 3
Kendisini veya yeni bir PropertyDescriptor
geri dönmelidir.
Property Decorator
İki paraketresi vardır ve bunlar sırasıyla bulunduğu sınıfın kendisi ve hedef aldığı property’nin ismidir.
Genellikle doğrulama yapmak için kullanılır.
function resit(target: any, key: string) {
const setter = function (newVal: number) {
if (newVal < 18) {
throw new Error("Yaş 18'den büyük olmalıdır.");
}
this[key] = newVal;
};
// Property'i tanımlama
Object.defineProperty(target, key, {
set: setter,
enumerable: true,
configurable: true,
});
};
class C{
yas: number
};
Geri dönüş değeri yoktur.
Accessor Decorator
Method dekoratörleriyle aynı parametreleri
alır. TypeScript get
ve set
erişimcileri için tek bir Property Descriptor üretir bu
yüzden işlem yapılırken bu göz önünde bulundurulmalıdır. Bu nedenle descriptor
yapısı method dekoratörlerinden farklıdır.
Descriptor içinde şu key’ler bulunur:
- get
- set
- configurable
- enumerable
Genellikle erişim yapılabilecek durumları düzenlemede, erişim için tetiklendiğinde log’lama yapmak gibi işlemlerde kullanılır.
Kendisini veya yeni bir PropertyDescriptor
geri dönmelidir.
Parameter Decorator
Üç tane parametresi bulunur ve bunlar sırasıyla: bulunduğu sınıf, bulunduğu method’un ismi ve o method’un kaçıncı parametresi olduğunun index’idir.
genellikle parametrelerin doğrulanması için kullanılır.
function resit(target: any, key: string, index: number) {
const originalMethod = target[key];
// Hedef methodun yerine yeni bir method tanımlıyoruz
target[key] = function (...args: any[]) {
const paramValue = args[index];
if (paramValue < 18) {
throw new Error("Parametre değeri 18'den büyük olmalıdır.");
}
return originalMethod.apply(this, args);
};
}
class C{
createUser(isim: string, yas: number) {
//...
}
}
Geriye bir değer döndürmez.
Decorator Factory
Şu ana kadar nasıl dekoratör oluşturabileceğimizi sistemin bize hangi bilgileri sağladığını ve bunlarla ne yapabileceğimizi gördük peki kendimiz de dışarıdan kendimiz parametre almak istediğimizde ya da dekoratör çağrılmadan önce de bir şeyler yapmak istedimizde ne yapmalıyız sorusuya baş başayız.Bu sorunun cevabı oldukça basit. “Dekoratörü de bir fonksiyonla sarmalıyız.”
Tek yapmamız gereken almak istediğimiz parametreleri dışarıdan almak ve geriye bir dekoratör döndermek.
function büyükMü(sayi){
return function(target, propertyIsmi){
if(target[propertyIsmi] > sayi)
throw new Error(`${propertyIsmi} ${sayi}'dan büyük olmalıdır.${propertyIsmi}:${target[propertyIsmi]}`);
}
}
class Kullanici{
üyükMü(18)
yas: number;
}
Not: Decorator factory ile oluşturulan
dekoratörler mutlaka ()
ile kullanılmalıdır aksi taktirde sadece factory gövdesi çalışmaktadır.
Örnek Senaryo:
Şu ana kadar öğrendiklerimizle basit bir
routing uygulaması yazabiliriz. Bu uygulama gelen Class ve methodlara verilen dekoratörlere göre gelen
istekleri yönlendirecektir. Class’lara path(p: string)
isteğin geleceği path’in ana kısmını method’lara ise get
, post
,
delete
gibi dekoratörlerle dinleyeceği methodu ve varsa
ana path üzerine eklenecek şeyi bilirecektir. Ana uygulama tüm class’ların metalarını toplayıp gelen
istekreki path’i ve method’u parçalayarak ilgili method’u çağıracaktır.
Örnek sınıf:
@path('/users')
class UserController{
@get('/:id')
getUser(request, response){}
@get()
getUsers(req, res){}
@post()
createUser(req, res){}
}
Kaynakça:
Decorators make the world of TypeScript better. People use lots of libraries built based on this awesome feature, for…saul-mirone.github.io