데코레이터는 앞에서 말한 것처럼 “클래스”, “메서드”, “접근자”, “프로퍼티”, “파라미터”에 적용할 수 있다. 데코레이터는 @expression 형식으로 적용하는데, 여기에서 expression 은 반드시 함수여야 한다.
decorator는 class의 다양한 property 및 method의 정의를 수정 및 교체하는 function이다.
decorator는 runtime에 호출된다 (즉, class instance를 생성하지 않아도 호출됨)
데코레이터는 @decorator과 같이 사용할 수 있으며 @[name]의 형식일 때 name에 해당하는 이름의 함수를 참조하게 된다.
이렇게 데코레이터로 정의된 함수는 데코레이터가 적용된 메소드가 실행되거나 클래스가 new라는 키워드를 통해 인스턴스화 될 때가 아닌 런타임 때 실행
된다. 즉, 매번 실행되지 않는다.
"target": "es2016",
"experimentalDecorators": true
class decorator는 클래스의 생성자를 유일한 인수로 호출한다. 즉, 인자가 하나이다.
class를 리턴한다.
function Logger(constructor: Function) {
console.log('logger');
console.log(constructor);
}
@Logger
class Person {
name = 'max';
constructor() {
console.log('creating person object...');
}
}
const pers = new Person();
console.log(pers);
데코레이터 팩토리는 데코레이터가 런타임에 호출할 표현식을 반환하는 함수이다.
팩토리 함수와 함께 실행된다면 데코레이션 함수가 사용하는 값을 커스터마이즈할 수 있다.
function Logger(logString: string) {
return function (constructor: Function) {
console.log(logString);
console.log(constructor);
};
}
@Logger('LOGGING')
class Person {
name = 'max';
constructor() {
console.log('creating person object...');
}
}
const pers = new Person();
console.log(pers);
function WithTemplate(template: string, hookId: string) {
// 존재하지만 불필요하기 때문에 _(언더바)로 표시
return function (_: Function) {
const hookEl = document.getElementById(hookId);
if (hookEl) {
hookEl.innerHTML = template;
}
};
}
@WithTemplate('<h1>temp</h1>', 'app')
class Person {
name = 'max';
constructor() {
console.log('creating person object...');
}
}
const pers = new Person();
console.log(pers);
function WithTemplate(template: string, hookId: string) {
return function (constructor: any) {
const hookEl = document.getElementById(hookId);
const p = new constructor();
if (hookEl) {
hookEl.innerHTML = template;
hookEl.querySelector('h1')!.textContent = p.name;
}
};
}
@WithTemplate('<h1>temp</h1>', 'app')
class Person {
name = 'max';
constructor() {
console.log('creating person object...');
}
}
const pers = new Person();
console.log(pers);
데코레이터는 아래에서부터 실행된다.
데코레이터 팩토리는 함수를 명시한 순서로 실행된다.
function Logger(logString: string) {
console.log('Logger Factory');
return function (constructor: Function) {
console.log('logger');
console.log(logString);
console.log(constructor);
};
}
function WithTemplate(template: string, hookId: string) {
console.log('Template Factory');
return function (constructor: any) {
console.log('template');
const hookEl = document.getElementById(hookId);
const p = new constructor();
if (hookEl) {
hookEl.innerHTML = template;
hookEl.querySelector('h1')!.textContent = p.name;
}
};
}
@Logger('Logging')
@WithTemplate('<h1>temp</h1>', 'app')
class Person {
name = 'max';
constructor() {
console.log('creating person object...');
}
}
const pers = new Person();
console.log(pers);
// 결과
Logger Factory
Template Factory
template
creating person object...
logger
Logging
class Person {
constructor() {
this.name = 'max';
console.log('creating person object...');
}
}
creating person object...
Person {name: 'max'}
target과 propertyName이라는 두가지 인자를 가진다.
리턴은 프로퍼티 description 형태이다.
function Log(target: any, propertyName: string | Symbol) {
console.log(target, propertyName);
//=> {constructor: ƒ, getPriceWthTax: ƒ}'title'
}
class Product {
@Log
title: string;
private _price: number;
set price(val: number) {
if (val > 0) {
this._price = val;
} else {
throw new Error('Invalid price - should be positive!');
}
}
constructor(t: string, p: number) {
this.title = t;
this._price = p;
}
getPriceWthTax(tax: number) {
return this._price * (1 + tax);
}
}
function Log2(target: any, name: string, descriptor: PropertyDescriptor) {
console.log('Accesstor decorator');
console.log(target);
console.log(name);
console.log(descriptor);
}
class Product {
title: string;
private _price: number;
@Log2
set price(val: number) {
if (val > 0) {
this._price = val;
} else {
throw new Error('Invalid price - should be positive!');
}
}
constructor(t: string, p: number) {
this.title = t;
this._price = p;
}
getPriceWthTax(tax: number) {
return this._price * (1 + tax);
}
}
// 결과
Accesstor decorator
{constructor: ƒ, getPriceWthTax: ƒ}
price
{get: undefined, enumerable: false, configurable: true, set: ƒ}
function Log3(target: any, name: string | Symbol, descriptor: PropertyDescriptor) {
console.log('Method decorator');
console.log(target);
console.log(name);
console.log(descriptor);
}
class Product {
@Log
title: string;
private _price: number;
@Log2
set price(val: number) {
if (val > 0) {
this._price = val;
} else {
throw new Error('Invalid price - should be positive!');
}
}
constructor(t: string, p: number) {
this.title = t;
this._price = p;
}
@Log3
getPriceWthTax(tax: number) {
return this._price * (1 + tax);
}
}
// 결과
Method decorator
{constructor: ƒ, getPriceWthTax: ƒ}
getPriceWthTax
{writable: true, enumerable: false, configurable: true, value: ƒ}
function Log4(target: any, name: string | Symbol, position: number) {
console.log('Method decorator');
console.log(target);
console.log(name);
console.log(position);
}
class Product {
title: string;
private _price: number;
set price(val: number) {
if (val > 0) {
this._price = val;
} else {
throw new Error('Invalid price - should be positive!');
}
}
constructor(t: string, p: number) {
this.title = t;
this._price = p;
}
getPriceWthTax(@Log4 tax: number) {
return this._price * (1 + tax);
}
}
// 결과
Parameter decorator
{constructor: ƒ, getPriceWthTax: ƒ}
getPriceWthTax
0