데코레이터(stage 3) — 클래스, 메서드, 필드 데코레이터
데코레이터(Decorator)는 클래스, 메서드, 속성 등에 메타 프로그래밍 기능을 추가 하는 문법입니다. TypeScript 5.0에서 TC39 stage 3 표준 데코레이터를 지원합니다.
레거시 vs 표준 데코레이터
// tsconfig.json
{
"compilerOptions": {
// 레거시 데코레이터 (Angular, NestJS에서 사용)
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
// TS 5.0+ 표준 데코레이터는 별도 설정 불필요
// experimentalDecorators를 끄면 자동으로 표준 데코레이터 사용
}
}
이 글에서는 TC39 stage 3 표준 데코레이터 를 다룹니다.
클래스 데코레이터
// 클래스 데코레이터 — 클래스 자체를 수정하거나 래핑
function sealed(target: Function, context: ClassDecoratorContext) {
Object.seal(target);
Object.seal(target.prototype);
}
@sealed
class User {
name: string;
constructor(name: string) {
this.name = name;
}
}
클래스를 래핑하는 데코레이터
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 인스턴스 생성"
메서드 데코레이터
// 실행 시간을 측정하는 데코레이터
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"
필드 데코레이터
// 최솟값/최댓값을 강제하는 데코레이터
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 키워드와 함께 사용합니다.
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 이상이어야 합니다
데코레이터 팩토리
매개변수를 받는 데코레이터를 만들려면 함수를 반환하는 함수 를 작성합니다.
// 데코레이터 팩토리
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 [];
}
}
여러 데코레이터 조합
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와 표준 데코레이터는 호환되지 않는다
댓글 로딩 중...