Typescript

함수에 기능을 추가하는 데코레이터 함수가 있다

비숑주인 2025. 4. 11. 21:28

타입스크립트 5.0부터 데코레이터(Decorator) 기능이 정식으로 채택되었다. 이전에는 실험적인 기능으로 사용해야 했으나, 이제는 안정적인 기능으로 클래스 및 클래스 요소의 동작을 확장할 수 있다.

데코레이터는 클래스, 메서드, 필드 등 다양한 위치에 적용할 수 있으며, 코드의 중복을 줄이고 공통 로직을 추상화할 수 있는 강력한 도구이다.

 

1. 데코레이터란 무엇인가?

 

데코레이터는 클래스나 클래스 요소의 동작을 감싸거나 확장하는 함수이다. 아래와 같은 공통된 패턴이 여러 메서드에 반복되고 있다면 데코레이터를 고려할 수 있다.

class A {
  eat() {
    console.log('start');
    console.log('Eat');
    console.log('end');
  }
  work() {
    console.log('start');
    console.log('Work');
    console.log('end');
  }
  sleap() {
    console.log('start');
    console.log('Sleap');
    console.log('end');
  }
}

 

위 코드의 중복을 제거하고 다음과 같은 데코레이터로 바꿀 수 있다. 

 

2. 기본 데코레이터 예시

function startAndEnd(originalMethod: 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
  sleap() {
    console.log('Sleap');
  }
}

 

이제 eat()을 호출하면 start, Eat, end 순으로 로그가 출력된다.

3. 제네릭으로 타입 안전한 데코레이터 만들기

기존 데코레이터는 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;
}

 

4. 매개변수를 받는 데코레이터

데코레이터에 동작을 커스터마이징할 수 있는 매개변수를 넘기려면 고차 함수를 사용해야 한다.

function startAndEnd(start = 'start', end = 'end') {
  return function <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(context.name, start);
      const result = originalMethod.call(this, ...args);
      console.log(context.name, end);
      return result;
    }
    return replacementMethod;
  };
}
class A {
  @startAndEnd()
  eat() {
    console.log('Eat');
  }

  @startAndEnd('>>> 시작', '<<< 끝')
  sleap() {
    console.log('Sleap');
  }
}

 

sleap()을 실행하면 다음과 같이 출력된다

sleap >>> 시작
Sleap
sleap <<< 끝

 

5. 클래스 데코레이터와 addInitializer

클래스 전체를 장식하는 클래스 데코레이터도 가능하다. 또한 메서드 데코레이터에서는 context.addInitializer()를 통해 초기화 로직을 추가할 수 있다.

function log<T extends new (...args: any[]) => any>(
  value: T,
  context: ClassDecoratorContext
) {
  return class extends value {
    log(msg: string) {
      console.log(msg);
    }
  };
}
function bound(
  originalMethod: unknown,
  context: ClassMethodDecoratorContext<any>
) {
  const methodName = context.name;
  if (context.kind === 'method') {
    context.addInitializer(function () {
      this[methodName] = this[methodName].bind(this);
    });
  }
}

 

6. 데코레이터의 context 구조

데코레이터에서 두 번째 매개변수로 전달되는 context는 장식 대상에 대한 다양한 정보를 포함한다.

type Context = {
  kind: string;
  name: string | symbol;
  access: {
    get?(): unknown;
    set?(value: unknown): void;
    has?(value: unknown): boolean;
  };
  private?: boolean;
  static?: boolean;
  addInitializer?(initializer: () => void): void;
};

 

  • kind: 데코레이터 대상 종류 (class, method, getter, 등)
  • name: 메서드/필드 이름
  • access: 접근자 객체
  • addInitializer: 인스턴스 초기화 시 실행할 함수 등록

 

7. 정리

타입스크립트 5.0에서 정식 도입된 데코레이터는 다음과 같은 강점을 가진다.

  • 코드 중복 제거: 공통 로직을 재사용
  • 기능 확장: 기존 클래스나 메서드의 기능을 변경 없이 확장 가능
  • 타입 안전성 확보: 제네릭 기반으로 안정적인 코드 작성 가능
  • 인스턴스 초기화 제어: addInitializer()로 lifecycle hook 제공

데코레이터는 함수형 프로그래밍, 메타프로그래밍, OOP 코드 최적화 등 다양한 분야에서 유용하게 활용할 수 있으므로 적극적으로 익혀두는 것이 좋다.