nest는 데코레이터를 적극적으로 활용하는 프레임워크입니다.
데코레이터를 적절히 사용하면 횡단 관심사(cross-cutting concern)을 분리하며
관점 지향 프로그래밍을 적용한 코드를 작성할 수 있는데요
다른 언어에 익숙하신 분들은 자바의 애너테이션, 파이썬의 데코레이터를 떠올려주셔도 됩니다.
타입스크립트에서 데코레이터는
클래스 , 메서드, 접근자, 프로퍼티, 매개변수에 적용이 가능하며
각 요소의 선언부 앞에 @로 시작하는 데코레이터를 선언하게되면 데코레이터로 구현된 코드를 함께 실행해줍니다.
이러한 데코레이터는 여러개의 데코레이터를 함께 사용하는 데코레이터 합성을 할 수 있습니다.
함수를 중첩해서 사용할 때와 비슷하게 생각을 할 수 있는데요
@f
@g
test
이러한 형태로 작성된 데코레이터 코드는 f(g(x))와 같다고 볼 수 있습니다.
클래스 데코레이터
클래스 데코레이터는 클래스의 바로 앞에 선언되는 데코레이터입니다.
클래스 데코레이터는 클래스의 "생성자"에 적용되며 클래스 정의를 읽거나 수정할 수 있습니다.
바꾸어 말하면 클래스 데코레이터는 생성자를 리턴하는 함수여야 합니다.
선언 파일과 선언 클래스(declare class를 의미합니다.) 에는 사용할 수 없습니다.
function reportableClassDecorator(T extends { new (...args:any[]): {} })>(contsructor:T){
return class extends constructor {
reportingURL = 'example.com'
}
}
@reportableClassDecorator
class BugReport {
type = 'report'
title:string
constructor(t:string) {
this.title = t
}
}
const bug = new BugReport("need")
console.log(bug)
이러한 형태로 사용가능하며 이 코드의 출력 결과는
type, reportingURL, title을 프로퍼티로 가지고 있는 객체가 됩니다.
메서드 데코레이터
메서드 데코레이터는 메서드의 바로 앞에 선언됩니다.
메서드의 속성 설명자에(property descriptor)에 적용되며 메서드의 정의를 읽거나 수정할 수 있습니다.
선언 파일, 오버로드 메서드, 선언 클래스에 사용할 수는 없습니다.
메서드 데코레이터는 총 세개의 인수를 가져야하는데요
1. 정적 멤버가 속한 클래스의 생성자 함수이거나 인스턴스 멤버에 대한 클래스의 프로토타입
2. 멤버의 이름
3. 멤버의 속성 설명자 (PropertyDescriptor) 타입
이 세가지를 가집니다.
PropertyDescriptor라고 하니까 되게 낯설고 막 초면인 것 같고 그런데
조금 찾아보니 프로퍼티 어트리뷰트에서 공부했던 내용들이 이름만 살짝 달라진 느낌이네요
프로퍼티 플래그들을 이렇게 부르는건가? 싶은 느낌이다.
const obj = { a: 2 };
Object.getOwnPropertyDescriptor(obj, 'a');
/*
{
configurable: true
enumerable: true
value: 2
writable: true
}
*/
어..? configurable, enumerable, writable 오랜만이다!
싶어 반갑지만 얘네에 집중하느라 당연히 넘겼던 친구를 다시 한번 보자
value에는 그 프로퍼티가 갖고있는 값이 할당되어있다.
이것에 집중하여 메서드 데코레이터를 보면 이해가 쉬워진다.
function HandleError() {
return function(target:any , propertyKey:string, descriptor:PropertyDescriptor) {
console.log(target)
const method = descriptor.value
descriptor.value = function() {
try {
method()
} catch(e) {
console.log(e)
}
}
}
}
이 코드는 인수로 받은 descriptor의 value를 뽑아서 method에 할당해두고
descriptor.value에 새로운 익명함수를 할당한다.
그리고 try문으로 기존 descriptor.value가 갖고있던 밸류를 래핑해주는데
이렇게 데코레이터를 달아주면 지저분한 try,catch문을 데코레이터로 꾸며주는것만으로도
수행시켜줄 수 있다.
처음으로 데코레이터가 와닿은 포인트.
PropertyDescriptor는 다음과 같은 타입으로 정의되어있다.
interface PropertyDescriptor {
configurable?: boolean;
enumerable?: boolean;
value?: any;
writable?: boolean;
get?():any;
set?(v:any):void
}
configurable : 속성의 정의를 수정할 수 있는지 여부
enumerable : 열거형인지 여부
writable : 수정 가능 여부
오랜만에 보니 기억이 새록새록 나는 것 같다.
접근자 데코레이터
접근자 데코레이터는 접근자 (accessor) 바로 앞에 선언합니다.
접근자의 속성 설명자에 적용되며 접근자의 정의를 읽거나 수정할 수 있습니다.
접근자 데코레이터 역시 선언 파일과 선언 클래스에는 사용할 수 없어요
접근자 데코레이터가 반환하는 값은 해당 멤버의 속성 설명자가 됩니다!
그런데 접근자라는 개념이 좀 생소한데요.
쉽게 생각하면 getter, setter 함수들을 접근자라고 생각하면 됩니다.
function Enumerable(enumerable:boolean) {
return function (target:any, propertyKey:string, descriptor:PropertyDescriptor) {
descriptor.enumerable = enumerable
}
}
class Person {
constructor(private name:string) {}
@Enumerable(true)
get getName() {
return this.name
}
}
이러한 형태로 게터 세터 함수를 꾸며줄 수 있습니다.
속성 데코레이터
속성 데코레이터 역시 선언 파일 , 선언 클래스에서는 사용할 수 없습니다.
속성 데코레이터는 다음 두개의 인수를 가지는 함수여야 합니다.
1. 정적 멤버가 속한 클래스의 생성자 함수이거나 인스턴스 멤버에 대한 클래스의 프로토타입
2. 멤버의 이름
메서드 데코레이터나 접근자 데코레이터와의 가장 큰 차이는 세번째 인수 Property Descriptor가 없다는 것입니다.
공식문서에 따르면 반환값도 무시가 되는데 이는 현재 프로토타입의 멤버를 정의할 때
인스턴스 속성을 설명하는 메커니즘이 없으며
속성의 초기화 과정을 관찰하거나 수정할 방법이 없기 때문이라고 설명합니다.
function format(formatString:string) {
return function (target: any, propertyKey: string): any {
let value = target[propertyKey]
function getter () {
return `${formatString} ${value}`
}
function setter(newVal:string) {
value= newVal
}
return {
get:getter,
set:setter,
enumerable:true,
configurable:true
}
}
}
class Greeter {
@format('Hello')
greeting:string
}
const t = new Greeter()
t.greeting = 'world'
console.log(t.greeting)
이 코드는 t.greeting을 했을 때 위에서 정의해둔 getter 함수가 호출되며
사전에 매개변수로 전달한 Hello가 앞에 출력됩니다.
즉 Hello greeting의 형태가 되는 것이고
t.greeting에는 world를 할당해두었기 때문에
console.log(t.greeting)의 출력값은 Hello world 가 됩니다.
매개변수 데코레이터
매개변수 데코레이터는 생성자 또는 메서드의 매개변수에 선언되어 적용됩니다.
역시 선언파일, 선언 클래스에는 사용할 수 없습니다.
매개변수 데코레이터는 호출될 때 3가지 인수와 함께 호출되는데요
반환값은 무시됩니다.
1. 정적 멤버가 속한 클래스의 생성자 함수이거나 인스턴스 멤버에 대한 클래스의 프로토타입
2. 멤버의 이름
3. 매개변수가 함수에서 몇 번째 위치에 선언되었는지를 나타내는 인덱스
매개변수 데코레이터를 통해 매개변수의 유효성 검사를 할 수 있습니다.
매개변수 데코레이터는 단독으로 사용하는 것보다 함수 데코레이터와 함께 사용할 때 유용하게 쓰입니다.
Nest에서 API 요청 매개변수에 대해 유효성 검사를 할 때에 이와 같은 형태의 데코레이터를 많이 사용합니다.
import { BadRequestException } from "@nestjs/common";
function MinLength(min: number) {
return function (target:any , propertyKey: string , parameterIndex:number) {
target.validators = {
minLength: function (args:string[]) {
return args[parameterIndex].length >= min
}
}
}
}
function Validate(target:any, propertyKey:string, descriptor:PropertyDescriptor){
const method = descriptor.value
descriptor.value = function(...args) {
Object.keys(target.validators).forEach(key => {
if(!target.validators[key](args)) {
throw new BadRequestException()
}
})
method.apply(this, args)
}
}
class User {
private name: string;
@Validate
setName(@MinLength(3) name:string) {
this.name = name
}
}
const t = new User()
t.setName = new User()
console.log('-------')
t.setName('De')
'Nestjs' 카테고리의 다른 글
nestjs 컨트롤러, 프로바이더, 모듈 (0) | 2023.08.23 |
---|---|
NestJS 시작해보기 (0) | 2023.07.07 |