타입스크립트 교재 · 16편 / 20편

데코레이터 — class·method·property

함수를 클래스 정의에 직접 붙이는 메타 도구. ES2024 표준과 legacy 차이.

고급읽는 시간 7분2026-05-17
@decorator 가 클래스 메서드 위에 적용되는 도식

고급 파트 시작. 데코레이터는 클래스·메서드·속성 위에 @name 한 줄로 함수를 적용해 추가 동작을 입히는 도구. Angular·NestJS·TypeORM 등 OOP 색이 진한 프레임워크에서 광범위하게 사용. 16편은 ES2024 표준과 옛 legacy 형태의 차이, 그리고 4가지 데코레이터 종류까지.

ES2024 표준 vs Legacy

Legacy (옛)ES2024 표준 (현재)
tsconfigexperimentalDecorators: true5.0+ 기본 활성
@typesreflect-metadata 필요없어도 됨
매개변수 데코지원미지원 (제안 단계)
시그니처(target, key, descriptor)(value, context)

현재 상태. TS 5.0+ 는 ES2024 표준이 기본. Angular·NestJS·TypeORM 같은 라이브러리들은 아직 legacy 사용 — experimentalDecorators: true + reflect-metadata. 본 편은 ES2024 표준 위주.

class 데코레이터

// ES2024 표준
function logCreation<T extends new (...args: any[]) => any>(
  Target: T,
  context: ClassDecoratorContext
) {
  return class extends Target {
    constructor(...args: any[]) {
      super(...args);
      console.log(`[${context.name}] 인스턴스 생성`);
    }
  };
}

@logCreation
class User {
  constructor(public name: string) {}
}

new User("준성");   // "[User] 인스턴스 생성"

method 데코레이터

function logCall(
  method: Function,
  context: ClassMethodDecoratorContext
) {
  return function (this: any, ...args: any[]) {
    console.log(`${String(context.name)} 호출`, args);
    return method.apply(this, args);
  };
}

class Calc {
  @logCall
  add(a: number, b: number) {
    return a + b;
  }
}

new Calc().add(1, 2);   // "add 호출 [1, 2]" 출력 후 3

accessor (getter/setter) 데코레이터

function notNull(
  value: { get: () => any; set: (v: any) => void },
  context: ClassAccessorDecoratorContext
) {
  return {
    get: value.get,
    set(this: any, newValue: any) {
      if (newValue == null) throw new Error(`${String(context.name)} 은 null 불가`);
      value.set.call(this, newValue);
    },
    init(initialValue: any) {
      if (initialValue == null) throw new Error(`${String(context.name)} 초기값 null`);
      return initialValue;
    },
  };
}

class User {
  @notNull
  accessor name: string = "";
}

property 데코레이터 (field)

function defaultValue<T>(value: T) {
  return function (
    target: undefined,
    context: ClassFieldDecoratorContext
  ) {
    return function (this: any, initial: T | undefined) {
      return initial ?? value;
    };
  };
}

class Config {
  @defaultValue(3000)
  port!: number;

  @defaultValue("localhost")
  host!: string;
}

데코레이터 팩토리 — 인자 받기

// 데코레이터에 옵션을 주려면 한 단계 더 감싸기
function trace(prefix: string) {
  return function (method: Function, context: ClassMethodDecoratorContext) {
    return function (this: any, ...args: any[]) {
      console.log(`[${prefix}] ${String(context.name)}`);
      return method.apply(this, args);
    };
  };
}

class Service {
  @trace("DB")
  query(sql: string) { ... }

  @trace("API")
  fetch(url: string) { ... }
}

실전 — 캐싱 데코레이터

function memoize(method: Function, context: ClassMethodDecoratorContext) {
  const cache = new Map<string, any>();
  return function (this: any, ...args: any[]) {
    const key = JSON.stringify(args);
    if (cache.has(key)) return cache.get(key);
    const result = method.apply(this, args);
    cache.set(key, result);
    return result;
  };
}

class Stats {
  @memoize
  expensive(n: number): number {
    console.log("계산 중...");
    return n * n;
  }
}

const s = new Stats();
s.expensive(5);  // "계산 중..." 출력 후 25
s.expensive(5);  // 캐시 hit, 즉시 25

Legacy (옛) 데코레이터 — Angular·NestJS 가 여전히 사용

// 옛 형식 — tsconfig: experimentalDecorators: true
function Component(options: { selector: string }) {
  return function (target: Function) {
    Reflect.defineMetadata("selector", options.selector, target);
  };
}

@Component({ selector: "app-user" })
class UserComponent { }

// NestJS 스타일
@Controller("users")
class UserController {
  @Get(":id")
  findOne(@Param("id") id: string) { ... }
}

실전 권고. 새로 만드는 일반 코드는 데코레이터를 거의 안 씁니다 — 단순 함수·고차 함수로 충분. 프레임워크가 요구할 때(Angular·NestJS·TypeORM·class-validator) 만. 데코레이터는 강력하지만 "마법" 처럼 동작이 숨겨져 디버깅이 어려워요.

한 표 — 데코레이터 4종 (ES2024)

대상위치context.kind
classclass 위"class"
method메서드 위"method"
accessor (get/set)accessor 위"accessor", "getter", "setter"
field (속성)필드 위"field"

17편 — namespace 와 .d.ts

JS 라이브러리에 타입 입히기. DefinitelyTyped(@types/*) 의 동작 방식.

📚 쉽게 배우는 타입스크립트 교재
이전: 15편 비동기 타입 · 현재: 16편 (고급 시작) · 다음 → 17편 .d.ts · 진행: 16/20

© 2026 주나이테크(주) @JUNAITECH