Decorator
- TypeScript와 ES6의 Class 도입으로 클래스 및 클래스 멤버에 주석을 달거나 수정하는 것을 지원하기 위해 도입
- 클래스 선언과 멤버에 대한 주석과 메타 프로그래밍 구문을 모두 추가할 수 있는 방법을 제공
- JavaScript에 대한 stage 2 proposal
- 향후 변경될 가능성 有
- @expression 으로 사용
-- expression은 decorate된 선언(declaration)에 대해 정보와 함께 런타임에 호출될 함수를 평가(evaluate)함
예제
// @sealed 라는 decorator 생성
function sealed(target) {
// do something with 'target' ...
}
- 선언에 적용할 decorator를 커스터마이징학 싶으면 decorator factory를 사용
- decorator factory는 런타임에 decorator에 의해서 호출될 함수를 return하는 함수
예제
// decorator factory
function color(value: string) {
// this is the decorator factory
return function (target) {
// this is the decorator
// do something with 'target' and 'value'...
};
}
Decorator 구성(Composition)
- 1개의 선언에 복수개의 decorator를 사용 가능
한줄에 쓰기
@f @g x
여러줄에 쓰기
@f
@g
x
- 이 때 이들의 evaluation은 f(g(x))
... 난 이과를 나왔지만 수학은.... 안한지 오래되어 다 까먹었다.. 이제 수학 공식만 나오면 무섭다...
예제
function f() {
console.log("f(): evaluated");
return function (
target,
propertyKey: string,
descriptor: PropertyDescriptor
) {
console.log("f(): called");
};
}
function g() {
console.log("g(): evaluated");
return function (
target,
propertyKey: string,
descriptor: PropertyDescriptor
) {
console.log("g(): called");
};
}
class C {
@f()
@g()
method() {}
}
f(): evaluated
g(): evaluated
g(): called
f(): called
- 각 decorator의 evaluate는 위에서 아래로 (top-to-bottom)
- 결과는 아래에서 위로(bottom-to-top)
Decorator Evaluation
- instance 멤버에 대해 함수(Function), 접근자(Accessor), 속성(Proeprty), 매개변수(Parameter) decorator가 적용
- static 멤버에 대해 함수(Function), 접근자(Accessor), 속성(Proeprty), 매개변수(Parameter) decorator
- 생성자(Constructor)에는 parameter decorator가 적용
- Class에는 Class decorator가 적용
Class Decorator
- class 선언 직전에 선언
- class 생성자에 적용됨
- class 정의(definition)를 관찰하거나 수정될 때 사용 가능
- 선언한 class 내에서 사용 불가
- class decorator 내에 작성된 expression은 런타임에 함수처럼 사용됨
- decorate된 클래스의 생성자는 class decorator의 argument
예제
function classDecorator<T extends { new (...args: any[]): {} }>(
constructor: T
) {
return class extends constructor {
newProperty = "new property";
hello = "override";
};
}
@classDecorator
class Greeter {
property = "property";
hello: string;
constructor(m: string) {
this.hello = m;
}
}
console.log(new Greeter("world"));
Method Decorator
- 함수 선언 직전에 선언
- Class Decorator와 마찬가지로 함수 정의를 관찰하거나 수정할 때 사용
- mothod decorator 내 expression은 런타임에 함수처럼 호출
- ES5보다 낮은 JavaScript인 경우 무시
예제
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
@enumerable(false)
greet() {
return "Hello, " + this.greeting;
}
}
function enumerable(value: boolean) {
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
descriptor.enumerable = value;
};
}
Accessor Decorator
- accessor 선언 바로 직전에 선언
- accessor의 property descriptor에 적용됨
- accessor의 정의를 관찰, 수정하는 데 사용
- 선언 파일과 선언 클래스에서 사용 불가
- accessor decorator의 expression은 런타임에 1. 정적 멤버에 대한 클래스의 생성자 함수 또는 인스턴스 멤버에 대한 클래스의 프로토타입, 2. member name, 3. member의 property decorator가 arguement로 주어짐
- accessor decorator가 값을 반환하면 member의 proeprty descriptor로 사용됨
- ES5 미만의 JavaScript인 경우 무시
예제
class Point {
private _x: number;
private _y: number;
constructor(x: number, y: number) {
this._x = x;
this._y = y;
}
@configurable(false)
get x() {
return this._x;
}
@configurable(false)
get y() {
return this._y;
}
}
function configurable(value: boolean) {
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
descriptor.configurable = value;
};
}
Property Decorator
- property 선언 직전에 선언
- 선언 파일이나 선언 클래스에서 사용 불가
- proeprty decorator의 expresion은 1. 클래스의 프로토타입, 2. member name 을 argument로 사용하여 런타임에 함수로 호출됨
- property decorator는 특정 이름의 송석이 클래스에 대해 선언이 되었는지 관찰하는 데만 사용 가능
예제
class Greeter {
@format("Hello, %s")
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet() {
let formatString = getFormat(this, "greeting");
return formatString.replace("%s", this.greeting);
}
}
import "reflect-metadata";
const formatMetadataKey = Symbol("format");
function format(formatString: string) {
return Reflect.metadata(formatMetadataKey, formatString);
}
function getFormat(target: any, propertyKey: string) {
return Reflect.getMetadata(formatMetadataKey, target, propertyKey);
}
Parameter Decorator
- parameter 선언 직전에 선언
- class 생성자 또는 함수 선언을 위한 함수에 적용됨
- 선언 파일, 선언 클래스에 사용 불가
- 매개변수가 함수에서 선언되었는지 확인하기 위해서만 사용 가능
- parameter decorator의 expression은 런타임에 1. 정적 멤버에 대한 클래스의 생성자 함수 또는 인스턴스 멤버에 대한 클래스의 프로토타입, 2. member name, 3. parameter의 ordinal index 라는 argument와 함께 함수로 호출
- parameter decoratore의 반환 값은 무시됨
예제
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
@validate
greet(@required name: string) {
return "Hello " + name + ", " + this.greeting;
}
}
import "reflect-metadata";
const requiredMetadataKey = Symbol("required");
function required(
target: Object,
propertyKey: string | symbol,
parameterIndex: number
) {
let existingRequiredParameters: number[] =
Reflect.getOwnMetadata(requiredMetadataKey, target, propertyKey) || [];
existingRequiredParameters.push(parameterIndex);
Reflect.defineMetadata(
requiredMetadataKey,
existingRequiredParameters,
target,
propertyKey
);
}
function validate(
target: any,
propertyName: string,
descriptor: TypedPropertyDescriptor<Function>
) {
let method = descriptor.value;
descriptor.value = function () {
let requiredParameters: number[] = Reflect.getOwnMetadata(
requiredMetadataKey,
target,
propertyName
);
if (requiredParameters) {
for (let parameterIndex of requiredParameters) {
if (
parameterIndex >= arguments.length ||
arguments[parameterIndex] === undefined
) {
throw new Error("Missing required argument.");
}
}
}
return method.apply(this, arguments);
};
}
출처
https://www.typescriptlang.org/docs/handbook/decorators.html