TypeScript에서 decorator 기능을 이용하기 위해서는 tsconfig에서 experimentalDecorators 속성을 활성화 해야 한다.

target

클래스의 선언부에 쓰였을 경우, 클래스의 생성자 함수가 들어온다.

decorator 함수 내에서 클래스의 생성자 함수를 변형하여 반환한다면, decorator가 적용된 클래스의 생성자 함수가 이에 따라 변형된다.

데코레이터를 이용해 싱글톤을 구현한 예시는 다음과 같다.

function singleton<T extends { new (...args: any[]): {} }>(target: T) {
  let instance: T | null = null;
 
  return class extends target {
    constructor(...args: any[]) {
      if (instance) {
        return instance;
      }
      super(...args);
      instance = this as unknown as T;
    }
  };
}
 
@singleton
class DemoClass {
  constructor(private name: string) {}
  getName() {
    return this.name;
  }
}
 
const demo = new DemoClass("a");
console.log(demo.getName());
const demo2 = new DemoClass("b");
console.log(demo2.getName());

메서드 데코레이터의 경우 정적 메서드일 때 클래스의 생성자함수가, 인스턴스 메서드일 때 클래스의 프로토타입 객체가 들어온다.

property key

decorator가 적용된 요소의 이름을 나타낸다.

메서드 데코레이터의 경우
메서드의 이름이 들어온다.

property descriptor

decorator가 적용된 요소의 property descriptor 객체를 나타낸다.
메서드 데코레이터의 경우 해당 메서드의 property descriptor가 들어온다.

function autobind(_: any, _2: any, descriptor: PropertyDescriptor) {
  const originalMethod = descriptor.value;
  const newDescriptor: PropertyDescriptor = {
    enumerable: false,
    configurable: true,
    get() {
      return originalMethod.bind(this);
    },
  };
  return newDescriptor;
}
 
class DemoClass {
  message = "NAME";
 
  @autobind
  getName() {
    console.log(this.message);
  }
}
 
const demo = new DemoClass();
const button = document.getElementById("button")!;
button.addEventListener("click", demo.getName);