[TS] 6. Decorators
이번포스팅에서는현재JavaScript에서도ts39/proposal stage‑2에올라
와있는 Decorator 에대해알아보겠습니다.
Table of contents
Setup
Intro
Decorator to method
Decorator to class
Decorator with parameter
Setup
자바스크립트babel환경에서데코레이터를테스트해보기 위해서는babel 플
러그인이추가적으로필요합니다.
 babel-core 를기본으로하며, babel‑plugin을추가적으로설치해줍니다.
{
//...
"plugins": ["transform-decorators-legacy"]
}
해당프로젝트의babel설정을담고 있는 .babelrc 파일에설치한플러그인
을추가해줍니다. 보다구체적인해당개발환경은여기를참고해주세요.
$ npm install babel-core babel-plugin-transform-decorators-lega
TyeScript에서는 tsconfig.json 의 compilerOption 을다음과 같이
변경해줍니다.
{
"compilerOptions": {
"target": "ES5",
"experimentalDecorators": true
}
}
Intro
TypeScript(JavaScript)에서 @ 이라는character로사용하는문법을
 Decorator(데코레이터) 라고 합니다. 자바를경험해보신분이라면
 Annotation 인가? 라고 생각하기 쉬운데요, 조금 다릅니다. 데코레이터는
함수라고 할수있습니다. 데코레이터는말그대로코드조각을장식해주는역
할을하며타입스크립트에서는그 기능을함수로구현할수있습니다.
Decorator는클래스선언, 메서드, 접근 제어자, 속성또는매개 변수에첨부
할수있는특별한종류의선언입니다. 데코레이터는 @expression 형식을
사용하는데, expression은데코레이팅된선언에대한정보와함께존재하며
이는런타임에호출됩니다.
참조(reference)
데코레이터는 @decorator 과 같이사용할수있으며 @[name] 의형식일
때 name 에해당하는이름의함수를참조하게 됩니다.
실행시점(execute time)
이렇게 데코레이터로정의된함수는데코레이터가 적용된메소드가 실행되거
나클래스가  new 라는키워드를통해인스턴스화될때가 아닌런타임때실
행됩니다. 즉, 매번실행되지않습니다.
그럼 데코레이터가 메소드에 적용되는 경우, 클래스에 적용되는 경우, 프로퍼
티에 적용되는 경우 이렇게 세 가지로 나누어 코드를 살펴보겠습니다.
Decorator to Method
메소드에적용되는경우
우선데코레이터로사용할 chaining 이라는함수를정의해줍니다.
위함수는추후메소드에 @chaining 형식으로사용될함수입니다.  @ 과 함
께함수가 호출되는경우받게 되는파라미터는다음과 같습니다.
target : 속성을정의하고자하는객체
name : 속성의이름
descriptor : 새로정의하고자하는속성에대한설명
function chaining(target: any, key: string, descriptor: Propert
console.log(target); // {bark: f, constructor: f}
console.log(key); // bark
console.log(descriptor); // {value: f, writable: true, enumer
}
이는 Object.defineProperty() 를통해이를정의하고 있기 때문입니다.
 target 은해당메소드가 속해있는클래스프로토타입을가리키게 되며
 Pet 의프로토타입에는 constructor 와 bark 메소드가 있는것을확인
할수있습니다.  name 은데코레이터가 적용된메소드의이름이됩니다.
 descriptor 는 defineProperty 에서정의할수있는각각의속성값들이
됩니다.
Object의 defineProperty 에해당하는보다자세한내용은여기에서살펴
보실수있습니다. 그럼각각을활용해서 chaining 기능을구현해보겠습니
다.
descriptor의 value 가 데코레이터가 적용된함수, 즉실행대상이라고 할
수있습니다.  descriptor.value 를재정의(override)하기 전에 fn 이라
는변수로caching해둔다음, 호출한후의일을정의하기 위해위와같이재정
의해줍니다. 재정의하기 전caching 해둔함수를호출하기 위해서 apply 
함수를사용했습니다. 어떠한변수가 얼만큼전달될지모르니rest parameter
를통해 fn 을호출해주는코드입니다.
// Decorator to method
function chaining(target: any, key: string, descriptor: Propert
const fn: Function = descriptor.value;
descriptor.value = function(...args: any[]) {
fn.apply(target, args);
return target;
}
}
위와같이 descriptor.value 가 재정의되면 chaining 이적용된메소
드는재정의된대로호출되게 됩니다.
 apply 함수에대한내용은여기를참고해주세요.
class Pet {
@chaining
bark() {
}
}
위와같이적용해보겠습니다.
const pet = new Pet();
 Pet 클래스에서 bark 라는메소드는 Pet.prototype.bark 로됩니다.
class syntax 내부에서위코드에서는 bark 라는메소드가  Pet 의
prototype의프로퍼티로추가되기 전에 decorate 함수가 실행되어본래
 bark 라는메소드에서정의된것에추가적인'장식' 을더해prototype에추
가되도록합니다.
만약compile target이ES5보다낮다면 PropertyDescriptor 값
으로 undefined 이전달됩니다.
pet.bark().bark();
위데코레이터의효과로 return this; 를해주지않아도chaining 기능을
사용하여메소드를호출할수있습니다.
Decorator to Class
하지만데코레이터가 class에적용되었을때는그 signature가 조금 달라집니
다. 클래스데코레이터는클래스선언바로전에선언됩니다. 클래스데코레이
터는클래스생성자에적용되며클래스정의를관찰, 수정또는대체하는데사
용할수있습니다.
클래스데코레이터가 값을반환하면클래스선언을제공된생성자함수로바꿉
니다.
function component(target, name, descriptor) {
console.log(target); // ...
console.log(name); // undefined
console.log(descriptor); //undefined
}
메소드에데코레이터를적용하듯이데코레이터함수를선언하면올바른선언
을할수없습니다. 클래스에적용되는데코레이터함수에전달되는인자는
 constructor 하나입니다. 제대로된데코레이터선언은다음과 같습니다.
클래스에적용되는데코레이터함수내에서새로운생성자함수를반환하면원
래프로토타입을유지해야합니다. 런타임에데코레이터를적용하는로직은이
를수행하지않기 때문입니다. 위코드에서는기존의프로토타입을유지하기
위해적용되는클래스의 constructor 를 extends 합니다.
function classDecorator<T extends {new(...args:any[]):{}}>(
return class extends constructor {
newProperty = "new property";
}
}
@classDecorator
class Pet {
constructor(name: string) {
this.name = name;
}
}
const pet = new Pet("async");
console.log(pet.newProperty); // new Property
 classDecorator 데코레이터가 적용된 Pet 클래스의인스턴스에는
 newProperty 가 존재하지않지만데코레이터함수에서해당클래스의
constructor를재정의했기 대문에 newProperty 에접근할수있습니다.
Decorator with parameter
파라미터를받는데코레이터
데코레이터함수에인자를넘겨줄수있습니다. 이인자는무엇이든될수있습
니다. 예제코드로descriptor의 enumerable 속성을변경하는데코레이터
를만들어보겠습니다.
이렇게 정의하면 enumerableToFalse 이적용된메소드의enumerable 속
성은false가 됩니다. 위 enumerableToFalse 함수를한번감싸서반환하
는함수를만들면다음과 같습니다.
function enumerableToFalse(target: any, propertyKey: string
descriptor.enumerable = false;
};
이제이함수를데코레이터함수로사용할수있습니다.  value 에해당하는
값으로데코레이터를적용하는메소드의 enumerable 속성을제어할수있
습니다.
function enumerable(value: boolean) {
return function (target: any, propertyKey: string, descriptor
descriptor.enumerable = value;
};
}
class Pet {
@enumerable(false)
bark() {
//...
}
}
위와같이 false 라는인자를받는데코레이터를정의했습니다. 저인자에는
함수도들어갈 수있으며데코레이터도들어갈 수있습니다.
마무리
target을ES5로지정해야제대로된데코레이터를사용할수있어서아직한
계가 있는Decorator지만React에서는HOC(High‑Order‑Component)에
많이사용하고 있는Decorator 였습니다!
감사합니다.
6. Decorator in TypeScript end
Reference
TypeScript Official Document ‑ Generics
https://github.com/wycats/javascript‑decorators
https://medium.com/google‑developers/exploring‑es7‑
decorators‑76ecb65fb841
https://www.sitepoint.com/javascript‑decorators‑what‑they‑are/
https://cabbageapps.com/fell‑love‑js‑decorators/
https://javarouka.github.io/blog/2016/09/30/decorator‑
exploring/#class‑il‑gyeongu
https://github.com/jayphelps/core‑decorators

06. decorator