Nest는 데코레이터를 적극 활용합니다. 데코레이터를 잘 사용하면 횡단관심사를 분리하여 관점 지향 프로그래밍을 적용한 코드를 작성할 수 있습니다. 타입스크립트의 데코레이터
는 파이썬의 데코레이터
나 자바의 어노테이션
과 유사한 기능을 합니다. 클래스, 메서드, 접근자, 프로퍼티, 매개변수에 적용 가능합니다. 각 요소의 선언부 앞에 @
로 시작하는 데코레이터를 선언하면 데코레이터로 구현된 코드를 함께 실행합니다. 예를 들어 다음코드는 유저 생성 요청의 본문을 DTO로 표현한 클래스입니다.
class CreateUserDto { @IsEmail() @MaxLength(60) readonly email: string; @IsString() @Matches(/^[A-Za-z\d!@#$%^&*()]{8,30}$/) readonly password: string; }
사용자는 얼마든지 요청을 잘못 보낼 수 있기 때문에 데코레이터를 이용하여 애플리케이션이 허용하는 값으로 제대로 요청을 보냈는지 검사하고 있습니다. email은 이메일 형식을 가진 문자열이어야 하고
@IsEmail()
그 길이는 최대 60자이어야 합니다.
@MaxLength(60)
password는 문자열이어야 하고@IsString()
주어진 정규표현식에 적합해야 합니다.@Matches(...)
데코레이터는 타입스크립트 스펙에서 아직 실험적인 기능입니다. 다시 1장에서 초기화한 프로젝트의 루트에 있는 tsconfig.json
파일을 보겠습니다. tsconfig.json 파일은 타입스크립트의 빌드환경을 정의한 파일입니다.
{ "compilerOptions": { ... "experimentalDecorators": true, ... } }
experimentalDecorators 옵션이 true로 설정되어 있습니다. 이 옵션을 켜야 데코레이터를 사용할 수 있습니다. 비록 실험적인 기능이지만 매우 안정적이며 수많은 프로젝트에서 사용하고 있습니다.
데코레이터는 위에서 봤던 것처럼 @expression
과 같은 형식으로 사용합니다. 여기서 expression은 데코레이팅 된 선언(데코레이터가 선언되는 클래스, 메서드 등)에 대한 정보와 함께 런타임에 호출되는 함수여야 합니다. 다음과 같은 메서드 데코레이터가 있고 이 데코레이터를 test라는 메서드에 선언했습니다. 여기서 deco 함수에 인자들이 있는데 메서드 데코레이터로 사용하기 위해서는 이렇게 정의해야 합니다.
function deco(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
console.log('데코레이터가 평가됨');
}
class TestClass {
@deco
test() {
console.log('함수 호출됨');
}
}
const t = new TestClass();
t.test();
이제 TestClass를 생성하고 test 메서드를 호출하면 다음과 같은 결과가 콘솔에 출력됩니다.
데코레이터가 평가됨
함수 호출됨
만약 데코레이터에 인자를 넘겨서 데코레이터의 동작을 변경하고 싶다면 데코레이터 팩토리, 즉 데코레이터를 리턴하는 함수를 만들면 됩니다. 위의 예시를 다음과 같이 value라는 인자를 받도록 바꿔보겠습니다.
function deco(value: string) {
console.log('데코레이터가 평가됨');
return function (target: any, propertykey: string, descriptor: PropertyDescriptor) {
console.log(value);
}
}
class TestClass {
@deco('HELLO')
test() {
console.log('함수 호출됨');
}
}
결과는 다음과 같습니다.
데코레이터가 평가됨
HELLO
함수 호출됨
만약 여러개의 데코레이터를 사용한다면 수학에서의 함수 합성과 같이 적용됩니다. 다음 데코레이터 선언의 합성 결과는 f(g(x))
와 같습니다.
@f
@g
test
여러 데코레이터를 사용할 때 다음 단계가 수행됩니다.
- 각 데코레이터의 표현은
위에서 아래로 평가(evaluate)
됩니다.- 그런 다음 결과는
아래에서 위 함수로 호출(call)
됩니다.
function first() {
console.log("first(): factory evaluated");
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
console.log("first(): called");
}
}
function second() {
console.log("second(): factory evaluated");
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
console.log("second(): called");
}
}
class ExampleClass {
@first()
@second()
method() {
console.log('method is called');
}
}
first(): factory evaluated
second(): factory evaluated
second(): called
first(): called
method is called
이제 타입스크립트가 지원하는 5가지 데코레이터를 알아봅시다.
이름 그대로 클래스 데코레이터는 클래스 바로 앞에 선언됩니다. 클래스 데코레이터는 클래스의 생성자에 적용되어 클래스 정의(definition)를 읽거나 수정할 수 있습니다.
선언 파일
과선언 클래스(declare class)
내에서는 사용할 수 없습니다.
다음 코드는 클래스에 reportingURL 속성을 추가하는 클래스 데코레이터의 예입니다.
BugReport 클래스에 선언되어 있지 않은 새로운 속성이 추가되었습니다.
클래스의 타입이 변경되는 것은 아닙니다. 타입 시스템은 reportingURL을 인식하지 못하기 때문에 bug.reportingURL과 같이 직접 사용할 수 없습니다.
메서드 데코레이터는 메서드 바로 앞에 선언됩니다. 메서드의 속성 디스크립터에 적용되고 메서드의 정의를 읽거나 수정할 수 있습니다. 선언 파일
, 오버로드 메서드
, 선언 클래스
에 사용할 수 없습니다.
앞서 deco 메서드 데코레이터에서 보았던 것처럼 메서드 데코레이터는 다음 세 개의 인수를 가집니다.
- 정적 멤버가 속한 클래스의 생성자 함수이거나 인스턴스 멤버에 대한 클래스의 프로토타입
- 멤버의 이름
- 멤버의 속성 디스크립터 (
PropertyDescriptor
타입을 가짐)
만약 메서드 데코레이터가 값을 반환한다면 이는 해당 메서드의 속성 디스크립터가 됩니다.
메서드 데코레이터의 예를 보겠습니다. 함수를 실행하는 과정에서 에러가 발생했을 때 이 에러를 잡아서 처리하는 로직을 구현하고 있습니다.
configurable
: 속성의 정의를 수정할 수 있는지 여부enumerable
: 열거형인지 여부value
: 속성값writable
: 수정 가능 여부get
: getterset
: setter
접근자 데코레이터는 접근자 바로 앞에 선언합니다. 접근자의 속성 디스크립터에 적용되고 접근자의 정의를 읽거나 수정할 수 있습니다. 역시 선언 파일
과 선언 클래스
에 사용할 수 없습니다. 접근자 데코레이터가 반환하는 값은 해당 멤버의 속성 디스크립터가 됩니다.
특정 멤버를 열거가 가능한 지 결정하는 데코레이터의 예를 보겠습니다.
속성 데코레이터는 클래스의 속성 바로 앞에 선언됩니다.
역시 선언 파일
, 선언 클래스
에서 사용하지 못합니다. 속성 데코레이터는 다음 두 개의 인수를 가지는 함수입니다.
- 정적 멤버가 속한 클래스의 생성자 함수이거나 인스턴스 멤버에 대한 클래스의 프로토타입
- 멤버의 이름
메서드 데코레이터나 접근자 데코레이터와 비교했을 때 세 번째 인자인 속성 디스크립터가 존재하지 않습니다. 공식문서에 따르면 반환값도 무시되고, 이는 현재 프로토타입(prototype)의 멤버를 정의할 때 인스턴스 속성을 설명하는 메커니즘이 없고 속성의 초기화 과정을 관찰하거나 수정할 수 있는 방법이 없기 때문이라고 합니다.
예상하셨듯이 생성자 또는 메서드의 파라미터에 선언되어 적용됩니다. 역시 선언 파일
, 선언 클래스
에서 사용할 수 없습니다. 매개변수 데코레이터는 호출될 때 3가지의 인자와 함께 호출됩니다. 반환값은 무시됩니다.
- 정적 멤버가 속한 클래스의 생성자 함수이거나 인스턴스 멤버에 대한 클래스의 프로토타입
- 멤버의 이름
- 매개변수가 함수에서 몇 번째 위치에 선언되었는 지를 나타내는 인덱스
파라미터가 제대로 된 값으로 전달되었는지 검사하는 데코레이터를 만들어 보겠습니다. 매개변수 데코레이터는 단독으로 사용하는 것보다 함수 데코레이터와 함께 사용할 때 유용하게 쓰입니다.
Nest에서 API 요청 파라미터에 대해 유효성 검사를 할 때 이와 유사한 데코레이터를 많이 사용합니다.
데코레이터[@] | 역할 | 호출시 전달되는 인자 | 선언 불가능한 위치 |
---|---|---|---|
클래스 데코레이터 | 클래스의 정의를 읽거나 수정 | (constructor) | d.ts 파일, declare 클래스 |
메서드 데코레이터 | 메서드의 정의를 읽거나 수정 | (target, propertyKey, propertyDescriptor) | d.ts 파일, declare 클래스, 오버로드 메서드 |
접근자 데코레이터 | 접근자의 정의를 읽거나 수정 | (target, propertyKey, propertyDescriptor) | d.ts 파일, declare 클래스 |
속성 데코레이터 | 속성의 정의를 읽음 | (target, propertyKey) | d.ts 파일, declare 클래스 |
매개변수 데코레이터 | 매개변수의 정의를 읽음 | (target, proeprtyKey, parameterIndex) | d.ts 파일, declare 클래스 |
- 타입스크립트 소스코드를 컴파일 할 때 생성되는 파일로 타입시스템의 타입추론을 돕는 코드가 포함되어 있다. 소스파일의 이름은
d.ts
로 끝난다.Property Descriptor
: 속성의 특성을 설명하는 객체- 객체 프로퍼티를 객체 외에서 읽고 쓸 수 있는 함수. 쉽게 이야기해서 게터(getter)와 세터(setter). 타입스크립트에는 게터와 세터를 구현할 수 있는
get
,set
키워드가 있다.