Nest.js를 활용해서 API서버를 구현할 때, 데코레이터를 많이 사용하였지만 만들어진 사용법 외에는 잘 모르는 것 같아서 데코레이터에 대해서 공부해보았다. 데코레이터는 아직 자바스크립트에서 지원하는 것은 아니고, 타입스크립트에서만 사용할 수 있다. 데코레이터를 활용한 코드를 자바스크립트로 빌드한 것을 보면, Reflect 객체를 활용하여 구현한 것을 볼 수 있다. 데코레이터에 대해서 찾아보면서 Reflect를 참 많이 본 것 같은데, 공부를 해봐야 할 것 같다.
데코레이터는 기본적으로 함수의 형태로 정의된다.
function Log(target: any) {
console.log('decorator');
}
@Log
class Person {
name = 'MAX';
}
위와 같은 형태로 데코레이터를 만들고 사용할 수 있는데, 데코레이터는 class가 정의될 때 실행된다. 또한 데코레이터는 클래스에 직접 사용하는 것 이외에도 프로퍼티나 메서드, 파라미터에 사용할 수 있다. class에 직접적으로 사용할 경우에는 인자로 constructor를 받는다. 그러나, 프로퍼티나 메서드, 파라미터에 사용하게 될 경우 인자로 받는 것이 각기 다르므로 이에 대해서 알아둘 필요가 있다.
데코레이터는 데코레이터 팩토리를 통해 추가적인 인자를 받아 이용할 수 있다.
function Logger(loggingString: string) {
return function (constructor: Function) {
console.log(constructor);
console.log(loggingString);
};
}
@Logger('string to log')
class Person {
name = 'MAXIM';
}
위와 같은 형태로 데코레이터를 만들게 되면, 추가적인 인자를 받아서 이를 활용한 데코레이터를 익명 함수로 만들어서 사용할 수 있다.이 경우
import { Controller, Get } from '@nestjs/common';
@Controller('cats')
export class CatsController {
@Get()
findAll(): string {
return 'This action returns all cats';
}
}
실제로 Nest.js를 사용하게 되면 @Controller('cats')나 @Get()에서 함수를 실행하는 듯한 표현을 볼 수 있는데 이는 Decorator factory를 만들어서 일정 부분 커스텀된 데코레이터를 리턴하도록 하는 방식으로 데코레이터를 이용한 것으로 볼 수 있다. 마치 객체지향 프로그래밍에서 원하는 class를 정의하고, 필요할 때 상속, 확장하여 사용하는 것과 비슷한 느낌이었다.
function WithTemplate(hookId: string, template: string) {
return function (constructor: any) {
const p = new constructor();
const hookEl = document.getElementById(hookId);
if (hookEl) {
hookEl.innerHTML = template;
console.log(p);
}
};
}
@WithTemplate('app', '<h1>데코레이터 실습</h1>')
class Person {
name = 'MAX';
}
이런 식으로 추가적인 처리를 할 수 있으며, constructor를 인자로 받기 때문에 새로운 객체를 생성하는 것 또한 가능하다. 또한, 동시에 여러개의 데코레이터를 달아줄 수도 있다. 이 경우 factory는 일반적인 코드처럼 위에서부터 아래로 순차적으로 실행되지만 데코레이터의 경우 아래에서부터 위로 순차적으로 실행된다.
매서드, 프로퍼티, 접근자에 대해서도 데코레이터를 활용할 수 있다고 하였는데 이 경우 각각 받는 인자는 다음과 같다.
function ForProperty(target: any, propertyName: string) {
// target은 생성자 함수의 prototype이다.
// static 프로퍼티라면, constructor 함수를 받는다.
// 두번째 인자는 프로퍼티의 이름이다. (key값)
console.log(target);
}
function ForAccessor(
target: any,
name: string,
descriptor: PropertyDescriptor
) {
// target은 생성자 함수의 prototype이다.
// name은 해당 setter / getter의 대상의 이름이다.
// descriptor는 해당 프로퍼티에 대한 설명이다.
console.log(target);
console.log(name);
console.log(descriptor);
}
function ForMethod(
target: any,
name: string | Symbol,
descriptor: PropertyDescriptor
) {
// target은 생성자 함수의 prototype이다.
// name은 해당 메소드의 이름이다.
// descriptor는 해당 프로퍼티에 대한 설명이다.
console.log(target);
console.log(name);
console.log(descriptor);
}
function ForParameter(target: any, name: string | Symbol, position: number) {
// target은 생성자 함수의 prototype이다.
// name은 파라미터를 받는 함수의 대상의 이름이다.
// descriptor는 해당 프로퍼티에 대한 설명이다.
console.log(target);
console.log(name);
console.log(position);
}
class Product {
@ForProperty
title: string;
_price: number;
constructor(title: string, _price: number) {
this.title = title;
this._price = _price;
}
@ForAccessor
set price(value: number) {
if (value >= 0) {
this._price = value;
}
}
@ForMethod
getPriceWithTax(@ForParameter tax: number) {
return this._price * (1 + tax);
}
}
데코레이터는 값을 반환할 수 있다.
이 경우에도 위와 마찬가지로 어디에 데코레이터를 사용하느냐에 따라 다르다. class에 직접적으로 사용하는 데코레이터의 경우, 반환값이 constructor 함수를 대체하게 된다. 즉, 다음과 같이 사용할 수 있는 것이다.
function WithTemplate(template: string, hookId: string) {
console.log('TEMPLATE FACTORY');
return function<T extends { new (...args: any[]): {name: string} }>(
originalConstructor: T
) {
return class extends originalConstructor {
constructor(..._: any[]) {
super();
console.log('Rendering template');
const hookEl = document.getElementById(hookId);
if (hookEl) {
hookEl.innerHTML = template;
hookEl.querySelector('h1')!.textContent = this.name;
}
}
};
};
}
@WithTemplate('<h1>My Person Object</h1>', 'app')
class Person {
name = 'Max';
constructor() {
console.log('Constructed!');
}
}
이 경우에는, constructor를 조작할 수 있기 때문에 객체가 정의될 때가 아니라 객체가 생성될 때 실행될 로직을 작성할 수 있다. 본질적으로는 class를 정의할 때에 실행되지만 이것을 잘 이용하면 생성될 때에도 작동하도록 할 수 있는 것이다.
접근자와 메서드에 이용되는 데코레이터 또한 반환값을 이용할 수 있다. 이 경우, propertyDescriptor를 반환하게 된다.
'배운 것' 카테고리의 다른 글
미확인 도착지 (0) | 2022.06.08 |
---|---|
자바스크립트의 this란? (0) | 2022.06.06 |
징검다리 건너기 (0) | 2022.05.30 |
프로그래머스 지형 이동 (0) | 2022.05.27 |
프로그래머스 자물쇠와 열쇠 (0) | 2022.02.03 |