TypeScript Custom Decorator

haaaalin·2023년 5월 15일
0

Decorators

classclass 내부에서만 사용할 수 있다. 즉, class, class 내부의 property, accessor, method 그리고 parameter에 사용할 수 있다는 말 !

decorator의 특징

  • class의 다양한 property 및 method의 정의를 수정 및 교체하는 function
  • runtime에 호출됨 (class instance를 생성하지 않아도 호출됨)

class decorator

클래스의 생성자를 유일한 인수로 호출하며, 클래스의 프로퍼티를 변경하는 데코레이터이다.

Car(name, price) {
	this.name = name;
	this.price = price;
}
function CustomDecorator(constructor: any) {
  console.log(constructor); 
  return <any>class extends constructor {
    name = 'SM6';
    color = 'black';  
  }
}

파라미터를 받는 class decorator

function ClassDecorator2(param: any) {
  console.log(param);
  return function(constructor: any) {
    console.log(constructor);
    return <any>class extends constructor {
      someValue = param.someValue + ' world!';
    }
  }
}

이렇게 아래처럼 사용할 수 있다.

@ClassDecorator2({someValue: 'hello!'})
class Car {
  name: string;
  ...

method decorator

3가지 argument를 받는다.

  • class의 prototype
  • class에서 해당 method의 key
  • property descriptor

✏️ method에서의 property descriptor ✏️

value: 현재 값 value
writable: 수정 가능하면 true
enumarable: for (i in [1,2,3])처럼 순회가 가능하면 true
configurable: Property definition이 수정 및 삭제가 가능하면 true

에러 핸들링하는 decorator 만들기

class A {
  b: string = "Hello"

  get c(): string {
    return `${this.b} World!`
  }

  @LogError("에러가 발생했습니다!")
  d(e: string): void {
    console.log(e)
    throw new Error()
  }
}

위 class A의 메서드인 d()에 @LogError라는 decorator를 한 번 만들어보자.
@LogError는 d() 메서드에서 error 발생 시 핸들링할 수 있도록 만드는 decorator이다.

function LogError(errorMessage: string) {
  return function (target: any, key: string, desc: PropertyDescriptor): void {
    const method = desc.value

    desc.value = function (e: string) {
      try {
        method(e)
      } catch (err) {
        console.log(errorMessage)
      }
    }
  }
}

위 코드가 바로 @LogError 를 구현한 코드이다.
인자 값으로custom errorMessage를 받아 error가 발생하면 console에 출력하도록 했다.

좀 더 발전시켜, Parameter로 errorMessage 대신, function을 넘겼다면 해당 function을 호출하도록 만들 수도 있다.

parameter decorator

3가지 argument를 받는다.

  • class의 prototype
  • class에서 해당 method의 key
  • 해당 parameter의 index 번호
class A {
  b: string = "Hello"

  get c(): string {
    return `${this.b} World!`
  }

  d(
    @ParameterDecorator e: string,
    @ParameterDecorator f: string,
    @ParameterDecorator g: string
  ): void {
    console.log(e, f, g)
  }
}

아래는 ParameterDecorator를 구현한 코드이다.

function ParameterDecorator(target: any, key: string, index: number) {
  console.log(target)
  console.log(key)
  console.log(index)
}

실행 결과는 다음과 같다.

{ c: [Getter], d: [Function (anonymous)] }
d
2
{ c: [Getter], d: [Function (anonymous)] }
d
1
{ c: [Getter], d: [Function (anonymous)] }
d
0

나머지 property decorator, accessor decorator는 모두 이와 유사하다고 한다.
실제 이런 유용한 코드를 짜기 위해서는 reflect-metadata 라는 라이브러리를 사용해야 한다.

Metadata

특정 타입에 대한 metadata를 내보낼 수 있도록 tsconfig.json 파일에 emitDecoratorMetadata compiler options를 추가하자..!

{
  "compilerOptions": {
    "target": "ES5",
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  }
}

참고

profile
한 걸음 한 걸음 쌓아가자😎

0개의 댓글