데코레이터 함수

데코레이터 함수

TS v5.0 에서 데코레이터 함수가 정식으로 추가됨

  • 클래스의 기능을 증강하는 함수로 여러 함수에서 공통으로 수행되는 부분을 데코레이터로 만들어두면 좋다.

  • 데코레이터를 여러개 붙힐 수 도 있다.

  • 클래스 데코레이터의 경우 export나 export default 앞이나 뒤에 데코레이터를 붙일 수 있다.

    • 단 앞과 뒤 동시에 붙이는건 안됨

@Log export class C {

export @Log class C {

@Log
export class C {
// eat, work, sleep이 start와 end를 로깅하는 console.log가 중복됨
// 이렇게 중복이 있는 경우 데코레이터를 통해 중복을 제거
class A {
  eat() {
    console.log('start');
    console.log('Eat');
    console.log('end');
  }
  
  work() {
    console.log('start');
    console.log('Work');
    console.log('end');
  }
  
  sleep() {
    console.log('start');
    console.log('Sleep');
    console.log('end');}
}
  • 데코레이터를 통해 중복을 제거

function startAndEnd(originMethod: any, context: any) {
  function replacementMethod(this: any, ...args: any[]) {
    console.log('start');
    const result = originalMethod.call(this, ...args);
    console.log('end');
    return result;
  }
  return replacementMethod;
}

class A {
  @startAndEnd
  eat() {
    console.log('Eat');
  }
  
  @startAndEnd
  work() {
    console.log('Work');
  }
  
  @startAndEnd
  sleep() {
    console.log('Sleep');
  }
}
  • originalMethod 매개변수 eat, work, sleep 같은 기존 메서드

  • 이 메서드가 대체 메서드(replacementMethod)로 바뀐다고 생각

  • any 타이핑을 제거해보자.

function startAndEnd<This, Args extends any[], Return>(
  originalMethod: (this: This, ...args: Args) => Return,
  context: ClassMethodDecoratorContext<This, (this: This, ...args: Args) => Return>
) {
  function replacementMethod(this: This, ...args: Args): Return {
    console.log('start');
    const result = originalMethod.call(this, ...args);
    console.log('end');
    
    return result;
  }
  return replacementMethod;
}

Context

데코레이터 정보를 갖고 있는 매개변수

  • startAndEnd 데코레이터의 경우 클래스의 메서드를 장식하고 있으므로 context는 ClassmethodDecoratorContext가 된다.

  • 어떤 문법을 장식하냐에 따라 context의 타입을 교체하면 된다.

// context 객체 타입
type Context = {
  kind: string; // 데코레이터 유형, ClassDeceoratorContext라면 class 
  name: string | symbol; // 장식 대상의 이름
  access: { // has, get, set 등의 접근자를 모아둔 객체
    get?(): unknown;
    set?(value: unknown): void;
    has?(value: unknown): boolean;
  };
  private?: boolean; // private 여부
  static?: boolean; // static 여부
  addInitializer?(initializer: () => void): void; // 초기화(인스턴스 생성)힐 때 실행되는 메서
}

Context의 종류

  • ClassDecoratorContext: 클래스 자체를 장식할 때

  • ClassMethodDecoratorContext: 클래스 메서드를 장식할 때

  • ClassGetterDecoratorContext: 클래스의 getter를 장식할 때

  • ClassSetterDecoratorContext: 클래스의 setter를 장식할 때

  • ClassMemberDecoratorContext: 클래스의 멤버를 장식할 때

  • ClassAccessorDecoratorContext: 클래스 accessor를 장식할 때

  • ClassFiledDecoratorContext: 클래스 필드를 장식할 때

데코레이터도 함수이다.

함수이므로 매개변수를 가질 수 있다.

다만 고차함수를 활용해야 한다.

function startAndEnd(start = 'start', end = 'end') {
  return function RealDecorator<This, Args extends any[] , Return>(
    originalMethod: (this: This, ...args: Args) => Return,
    context: ClassMethodDecoratorContext<This, (this: This, ...args: Args) => Return>
  ) {
      function replacementMethod(this: This, ...args: Args): Return {
      console.log(contenxt.name, start);
      const result = originalMethod.call(this, ...args);
      console.log(context.name, end);
      
      return result;
    }
    return replacementMethod;
  }
}

class A {
  @sttartAndEnd('시작', '끝')
  sleep() {
    console.log('Sleep');
  }
}

또 다른 예시

function log<Input extends new (...args: any[]) => any> (
  value: Input,
  context: ClassDecoratorContext
) {

  if (context.kind === 'class') {
    return class extends value {
      constructor(...args: any[]) {
        super(args);
      }
      log(msg: string): void {
        console.log(msg);
      }
    };
    return value
  }
}

function bound(originalMethod: unknown, context: ClassMethodDecoratorContext<any>) {
  const methodName = context.name;
  if (context.kind === 'method') {
    context.addInitializer(function () {
      this[methodName] = this[methodName].bind(this);
    })
  }
}

@log
export class C {
  @bound
  @startAndEnd()
  eat() {
    console.log('Eat');
  }
  
  @bound @startAndEnd() work() {
    conosle.log('Work');
  }
}

Last updated