[Nest.js] Decorator

Jimin_Note·2022년 9월 6일
0

🐛node.js

목록 보기
4/5
post-thumbnail
  • Nest는 데코레이터를 적극 활용한다
  • 타입스크립트의 데코레이터는 파이썬의 데코레이터,자바의 어노테이션 과 유사하다.
  • 각 요소의 선언부 앞에 @로 시작하는 데코레이터를 선언하면 데코레이터로 구현된 코드를 함께 실행한다.

ex)

class CreateUserDto {
  @IsEmail() //이메일 형식을 가진 문자열 확인
  @MaxLength(60) //길이 최대 60자 확인
  readonly email: string; 

  @IsString() //password는 문자열
  @Matches(/^[A-Za-z\d!@#$%^&*()]{8,30}$/) //정규표현식
  readonly password: string; //readonly : 읽기전용 타입
}

tsconfig.json

{
  "compilerOptions": {
        ...
    "experimentalDecorators": true, //true로 해줘야 데코레이터 사용 가능
        ...
  }
}

🌟데코레이터 합성

여러개의 데코레이터를 사용한다면 수학에서의 함수 합성과 같이 적용
f(g(x))

@f
@g
test
  • 데코레이터의 표현은 위에서 아래도 evaluate
  • 결과는 아래에서 위로 call
function first() {
  console.log("first(): factory evaluated"); //1
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    console.log("first(): called"); //4
  };
}

function second() {
  console.log("second(): factory evaluated"); //2
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    console.log("second(): called");  //3
  };
}

class ExampleClass {
  @first()
  @second()
  method() {
    console.log('method is called'); //5
  } 
}

//<result>
first(): factory evaluated
second(): factory evaluated
second(): called
first(): called
method is called

🌟타입스크립트가 지원하는 데코레이터 5가지

✅ Class decorator

  • 클래스 바로 앞에 선언
  • 클래스의 생성자에 적용되어 클래스 정의를 읽거나 수정할 수 있다.
  • 선언 파일과 선언 클래스내에서는 사용 불가
//클래스 데코레이터 팩토리
function reportableClassDecorator<T extends { new (...args: any[]): {} }>(constructor: T) {  //T:제너릭타입
  return class extends constructor {
    reportingURL = "http://www.example.com";
  };
}

@reportableClassDecorator
class BugReport {
  type = "report";
  title: string;

  constructor(t: string) {
    this.title = t;
  }
}

const bug = new BugReport("Needs dark mode");
console.log(bug);

//<result>
{type: 'report', title: 'Needs dark mode', reportingURL: 'http://www.example.com'}

✅ Method decorator

  • 메서드 바로 앞에 선언
  • 선언 파일, 오버로드 메서드, 선언 클래스에서 사용불가
function HandleError() {
  //메서드 데코레이터가 가져야 하는 3개의 인자
  return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) { //descriptor는 해당 속성의 설명자 객체, PropertyDescriptor는 밑에서 설명
    console.log(target)
    console.log(propertyKey)
    console.log(descriptor)

    const method = descriptor.value;

    descriptor.value = function() {
      try {
        method();
      } catch (e) {
        // 에러 핸들링 로직 구현
        console.log(e);
      }
    }
  };
}

class Greeter {
  @HandleError()
  hello() {
    throw new Error('테스트 에러');
  }
}

const t = new Greeter();
t.hello();

PropertyDescriptor
: 객체 속성의 특성을 기술하고 있는 객체

interface PropertyDescriptor {
  configurable?: boolean;  // 속성의 정의를 수정할 수 있는지 여부
  enumerable?: boolean;    // 열거형인지 여부
  value?: any;             // 속성 값
  writable?: boolean;      // 수정 가능 여부
  get?(): any;             // getter
  set?(v: any): void;      // setter
}

✅ Accessor decorator

  • 접근자 바로앞에 선언
  • 선언 파일과 선언 클래스에서 사용불가
function Enumerable(enumerable: boolean) { // enumerable 속성을 데코레이터 인자로 결정
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    descriptor.enumerable = enumerable;
  }
}

class Person {
  constructor(private name: string) {}

  @Enumerable(true) //열거가능
  get getName() {
    return this.name;
  }

  @Enumerable(false) // 열거불가능
  set setName(name: string) {
    this.name = name;
  }
}

const person = new Person('jimin');
for (let key in person) {
  console.log(`${key}: ${person[key]}`);
}

//<result>
name: jimin
getName: jimin

//setName은 열거하지 못하게 되었기 때문에 for문에서 key로 받을 수 없다.

✅ Property Decorator

  • 속성 바로 앞에 선언
  • 선언 파일, 선언 클래스에서 사용불가
    메서드 데코레이터,접근자 데코레이터와 비교했을 때 세 번쨰 인자인 속성 디스크립터가 존재하지 않는다.
function format(formatString: string) {
  return function (target: any, propertyKey: string): any {
    let value = target[propertyKey];

    function getter() {
      return `${formatString} ${value}`;
    }  //데코레이터 인자로 들어온 formatString을 원래의 속성과 조합한 스트링으로 변환

    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);

//출력 :Hello World

Parameter Decorator

  • 생성자 또는 메서드의 파라미터에 선언
  • 선언 파일, 선언 클래스에서 사용 불가
  • Nest에서 API 요청 파라미터에 대해 유효성 검사를 할 때 이와 유사한 데코레이터 많이 사용
import { BadRequestException } from '@nestjs/common';

function MinLength(min: number) { //파라미터의 최소값을 검사하는 파라미터 데코레이터
  return function (target: any, propertyKey: string, parameterIndex: number) {
    target.validators = {  // validators 속성에 유효성을 검사하는 함수 할당
      minLength: function (args: string[]) {
        return args[parameterIndex].length >= min;
      }
    }
  }
}
//유효성 검사 :  parameterIndex에 위치한 인자의 길이가 최소값보다 같거나 큰지 검사
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) { //최소길이 3으로 지정
    this.name = name;
  }
}

const t = new User();
t.setName('jimin'); // 길이가 3이상이라 문제없음
console.log('----------')
t.setName('ji'); //길이가 3보다 작기 때문에 BadRequestException 발생

🌟결론

데코레이터전달되는 인자선언 불가능한 위치
클래스constructord.ts 파일, declare 클래스
메서드target,propertyKey,PropertyDescriptord.ts 파일,declare 클래스,오버로드 메서드
접근자target, propertyKey,propertyDescriptord.ts 파일,declare클래스
속성target, propertyKeyd.ts파일, declare 클래스
매개변수target,propertyKey,parameterIndexd.ts파일,declare클래스
profile
Hello. I'm jimin:)

0개의 댓글