ECMAScript | TypeScript

Typescript의 Decorator - 2. Property Decorator, 간단한 Dependency Injection

partner_jun 2017. 8. 15. 15:47

클래스의 프로퍼티에도 데코레이터를 사용할 수 있다.


먼저 예제에 사용할 간단한 클래스를 정의한다.

class Car {
private name: string;
private price: number;
private type: string;

constructor(name: string, price: number) {
this.name = name;
this.price = price;
}

public toString() {
return `${this.name}, ${this.type}, ${this.price}`;
}
}


또, 객체를 주입할 때 사용할 간단한 컨테이너를 정의하고 객체를 넣어둔다.

class Container {
private static map: {[key: string]: any} = {};

static add(key: string, value: string) {
Container.map[key] = value;
}

static get(key: string): string {
return Container.map[key];
}
}
Container.add('myType', 'Classic');
console.log(Container.get('myType')); // 'Classic'




1) 기본

프로퍼티에 사용할 데코레이터를 정의한다.

다른 데코레이터와 마찬가지로 첫 번째 함수에서는 데코레이터의 파라미터를, 리턴할 두 번째 함수에서는 데코레이터의 대상과 프로퍼티 이름을 받는 형태다.

function Inject(param: string) {
return function (target: any, decoratedPropertyName: string) {
/*
데코레이터의 파라미터를 검사하거나 수정하는 등의 작업을 할 수 있다.
Ex) throw new Error('invalid');
*/
console.log(target); // {toString: ƒ, constructor: ƒ}
console.log(decoratedPropertyName); // type
target[decoratedPropertyName] = Container.get(param); // target object의 property에 값 할당
};
}

target[decoratedPropertyname]에 값을 할당하면 해당 프로퍼티의 값이 바뀐다!


이렇게 정의한 데코레이터는 클래스를 정의할 때 사용할 수 있다.

class Car {
private name: string;
private price: number;
@Inject('myType') // 값 주입
private type: string;
let myCar = new Car('SM5', 2000);
console.log(myCar.toString()); // SM5, Classic, 2000




2) Lazy Initialization

재미있는 점은 클래스가 '정의될 때' 데코레이터 함수가 실행되어 클래스의 프로퍼티 값이 지정된다는 것이다.

때문에 클래스를 위와 같이 정의하면 컨테이너의 값을 수정해도 처음 값이 그대로 출력된다.

Container.add('myType', 'PE');
let myCar = new Car('SM5', 2000);
console.log(myCar.toString()); // SM5, Classic, 2000


어디서나 늘 그렇듯 Lazy한 처리를 위해서는 함수를 리턴해 값을 필요로 할 때 실행하게 하면 된다.

function Inject(param: string) {
return function (target: any, decoratedPropertyName: string) {
/*
데코레이터의 파라미터를 검사하거나 수정하는 등의 작업을 할 수 있다.
Ex) throw new Error('invalid');
*/
console.log("inject!");
target[decoratedPropertyName] = () => Container.get(param); // `값`이 아닌 `함수`를 리턴
};
}

class Car {
private name: string;
private price: number;
@Inject('myType') // 값 주입
private type: Function; // `type`은 `값`이 아니라 `함수`

constructor(name: string, price: number) {
this.name = name;
this.price = price;
}

public toString() {
return `${this.name}, ${this.type()}, ${this.price}`;
}
}

Car 클래스의 Type 프로퍼티가 값이 아닌 함수를 가지고 있게 수정한다.


Container.add('myType', 'PE');
let myCar = new Car('SM5', 2000);
console.log(myCar.toString()); // SM5, Classic, 2000

수정된 컨테이너의 값이 반영된다.



하지만 위 예제는 모든 Car 객체의 type 프로퍼티가 하나의 함수를 참조하고 있기 때문에 컨테이너의 값을 변환할 때마다 모든 Car 객체의 type 프로퍼티가 바뀔 것이다.

이 문제는 객체가 만들어질 때, 그러니까 constructor 함수에서 type 프로퍼티에 객체 고유의 함수를 등록하거나 Lazy하게 프로퍼티를 주입하는 새로운 클래스를 만드는 등 다양한 방법으로 해결할 수 있다.