데코레이터(Decorator)는 클래스, 메서드, 속성 등에 메타 프로그래밍 기능을 추가 하는 문법입니다. TypeScript 5.0에서 TC39 stage 3 표준 데코레이터를 지원합니다.

레거시 vs 표준 데코레이터

JSON
// tsconfig.json
{
  "compilerOptions": {
    // 레거시 데코레이터 (Angular, NestJS에서 사용)
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,

    // TS 5.0+ 표준 데코레이터는 별도 설정 불필요
    // experimentalDecorators를 끄면 자동으로 표준 데코레이터 사용
  }
}

이 글에서는 TC39 stage 3 표준 데코레이터 를 다룹니다.

클래스 데코레이터

TYPESCRIPT
// 클래스 데코레이터 — 클래스 자체를 수정하거나 래핑
function sealed(target: Function, context: ClassDecoratorContext) {
  Object.seal(target);
  Object.seal(target.prototype);
}

@sealed
class User {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
}

클래스를 래핑하는 데코레이터

TYPESCRIPT
function withLogging<T extends new (...args: any[]) => any>(
  target: T,
  context: ClassDecoratorContext
) {
  return class extends target {
    constructor(...args: any[]) {
      console.log(`${context.name} 인스턴스 생성`);
      super(...args);
    }
  };
}

@withLogging
class UserService {
  constructor(public name: string) {}
}

const service = new UserService('test');
// 콘솔: "UserService 인스턴스 생성"

메서드 데코레이터

TYPESCRIPT
// 실행 시간을 측정하는 데코레이터
function logged<T extends (...args: any[]) => any>(
  target: T,
  context: ClassMethodDecoratorContext
): T {
  function replacement(this: any, ...args: any[]) {
    console.log(`${String(context.name)} 호출됨`);
    const start = performance.now();
    const result = target.call(this, ...args);
    const end = performance.now();
    console.log(`${String(context.name)} 완료: ${(end - start).toFixed(2)}ms`);
    return result;
  }
  return replacement as T;
}

class Calculator {
  @logged
  add(a: number, b: number): number {
    return a + b;
  }

  @logged
  multiply(a: number, b: number): number {
    return a * b;
  }
}

const calc = new Calculator();
calc.add(1, 2);
// 콘솔: "add 호출됨"
// 콘솔: "add 완료: 0.01ms"

필드 데코레이터

TYPESCRIPT
// 최솟값/최댓값을 강제하는 데코레이터
function range(min: number, max: number) {
  return function (
    _target: undefined,
    context: ClassFieldDecoratorContext
  ) {
    return function (initialValue: number): number {
      if (initialValue < min) return min;
      if (initialValue > max) return max;
      return initialValue;
    };
  };
}

class Config {
  @range(1, 100)
  retries = 50;   // 50 (범위 내)

  @range(1000, 30000)
  timeout = 500;  // 1000 (최솟값으로 보정)
}

접근자 데코레이터(Accessor Decorator)

TypeScript 5.0에서 accessor 키워드와 함께 사용합니다.

TYPESCRIPT
function validated(
  target: ClassAccessorDecoratorTarget<any, number>,
  context: ClassAccessorDecoratorContext
): ClassAccessorDecoratorResult<any, number> {
  return {
    set(value: number) {
      if (value < 0) throw new Error(`${String(context.name)}은 0 이상이어야 합니다`);
      target.set.call(this, value);
    },
    get() {
      return target.get.call(this);
    },
  };
}

class Product {
  @validated
  accessor price: number = 0;
}

const p = new Product();
p.price = 1000;    // OK
// p.price = -100; // Error: price은 0 이상이어야 합니다

데코레이터 팩토리

매개변수를 받는 데코레이터를 만들려면 함수를 반환하는 함수 를 작성합니다.

TYPESCRIPT
// 데코레이터 팩토리
function deprecated(message: string) {
  return function <T extends (...args: any[]) => any>(
    target: T,
    context: ClassMethodDecoratorContext
  ): T {
    function replacement(this: any, ...args: any[]) {
      console.warn(`경고: ${String(context.name)}은 더 이상 사용되지 않습니다. ${message}`);
      return target.call(this, ...args);
    }
    return replacement as T;
  };
}

class Api {
  @deprecated('fetchUsers를 사용해주세요')
  getUsers() {
    return [];
  }
}

여러 데코레이터 조합

TYPESCRIPT
class Service {
  @logged
  @deprecated('newMethod를 사용해주세요')
  oldMethod() {
    // 데코레이터는 아래에서 위로 적용 (오른쪽에서 왼쪽)
    // deprecated가 먼저 적용되고, logged가 그 결과를 감쌈
  }
}

레거시 데코레이터와의 차이

특성레거시 (experimental)표준 (stage 3)
설정experimentalDecorators: true설정 불필요
매개변수(target, key, descriptor)(target, context)
메타데이터reflect-metadata직접 관리
매개변수 데코레이터지원미지원

면접 포인트: Angular, NestJS는 레거시 데코레이터를 사용하고, 새 프로젝트에서는 표준 데코레이터를 고려할 수 있습니다. 두 방식은 호환되지 않습니다.

정리

  • TC39 stage 3 표준 데코레이터는 TypeScript 5.0부터 지원된다
  • 클래스, 메서드, 필드, 접근자에 적용할 수 있다
  • 데코레이터 팩토리로 매개변수를 받는 데코레이터를 만든다
  • 여러 데코레이터는 아래에서 위로(안에서 밖으로) 적용된다
  • 레거시 experimentalDecorators와 표준 데코레이터는 호환되지 않는다
댓글 로딩 중...