TypeScript 데코레이터의 동작 원리 – 프레임워크가 쓰는 이유를 “실행 시점”으로 설명
데코레이터 실행 시점과 메타데이터 수집 역할, factory·autobind·validation 패턴과 주의점을 정리
Posted
By okorion
TypeScript 데코레이터의 동작 원리 – 프레임워크가 쓰는 이유를 “실행 시점”으로 설명
결론: 데코레이터는 “실행 로직”이 아니라 “정의 시점 메타데이터”를 만든다
데코레이터는 함수처럼 보이지만 호출 타이밍과 목적이 전혀 다르다. 데코레이터의 핵심 가치는 로직 실행이 아니라, 클래스/멤버가 정의될 때 정보를 수집·부착하는 데 있다. 이 지점을 이해하지 못하면 데코레이터는 곧바로 디버깅 지옥이 된다.
데코레이터는 “정의 시점”에 실행된다 (인스턴스 생성 전)
결론: 데코레이터는 객체가 만들어질 때가 아니라, 클래스가 로드될 때 실행된다.
실행 시점 구분
- 정의 시점: 클래스 선언이 평가될 때 즉시 실행
- 인스턴스 시점:
new로 객체를 만들 때
1
2
3
4
5
6
7
8
9
10
11
12
13
14
function Log(target: any) {
console.log("decorator executed");
}
@Log
class Example {
constructor() {
console.log("instance created");
}
}
// 출력 순서
// decorator executed
// instance created
흔한 오해 / 디버깅 포인트
- “인스턴스마다 데코레이터가 실행된다” → ❌
- “this에 접근할 수 있다” → ❌ (정의 시점에는 인스턴스가 없다)
데코레이터 종류는 “어디에 붙느냐”에 따라 역할이 다르다
결론: 데코레이터는 위치에 따라 받을 수 있는 정보가 완전히 다르다.
개념 정리
- 클래스 데코레이터: 생성자 함수
- 메서드 데코레이터: 프로토타입 + 메서드 이름 + descriptor
- 접근자 데코레이터: getter/setter descriptor
- 속성 데코레이터: 프로토타입 + 프로퍼티 이름 (descriptor 없음)
- 매개변수 데코레이터: 프로토타입 + 메서드 이름 + 인덱스
1
2
3
4
5
6
7
8
9
10
11
12
function LogMethod(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
console.log(propertyKey);
}
class Example {
@LogMethod
run() {}
}
실무 포인트
- 속성 데코레이터는 값에 접근할 수 없다 → 메타데이터 저장용
- 실제 동작 변경은 메서드/클래스 데코레이터에서만 가능
Decorator Factory는 “옵션을 전달하기 위한 필수 패턴”이다
결론: 인자를 받는 데코레이터는 항상 “함수를 한 번 더 감싼다”.
1
2
3
4
5
6
7
8
function Role(role: "admin" | "user") {
return function (target: any) {
target.prototype.role = role;
};
}
@Role("admin")
class User {}
실행 순서 핵심
Role("admin")실행 (정의 시점)- 반환된 함수가 실제 데코레이터로 실행
흔한 실수
- factory 없이 옵션을 바로 쓰려다 타입/실행 오류 발생
- factory 호출 시점과 decorator 적용 시점 혼동
autobind 데코레이터는 “this 바인딩 문제”를 구조적으로 해결한다
결론: autobind는 이벤트 핸들러에서 this 유실을 막는 설계 도구다.
autobind 구현 예제
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function Autobind(
_: any,
_2: string,
descriptor: PropertyDescriptor
) {
const original = descriptor.value;
return {
configurable: true,
get() {
return original.bind(this);
}
};
}
class ButtonHandler {
message = "clicked";
@Autobind
handleClick() {
console.log(this.message);
}
}
대안과 비교
- arrow function: 간단하지만 메서드 재사용/상속 불리
- bind in constructor: 보일러플레이트 증가
- decorator: 선언적, 프레임워크 친화적
validation 데코레이터는 “검증 로직”이 아니라 “검증 규칙 저장”이다
결론: 데코레이터는 검증을 하지 않는다. 검증 정보만 모은다.
간단한 validation 메타데이터 설계
1
2
3
4
5
6
7
8
9
10
11
12
13
14
const validators: Record<string, string[]> = {};
function Required(target: any, prop: string) {
const className = target.constructor.name;
validators[className] = [
...(validators[className] ?? []),
prop
];
}
class User {
@Required
name!: string;
}
1
2
3
4
function validate(obj: any): boolean {
const rules = validators[obj.constructor.name] ?? [];
return rules.every(prop => !!obj[prop]);
}
핵심 함정
- TypeScript 타입은 런타임에 사라진다
- 데코레이터만으로는 값 검증 불가
- 반드시 별도의 validate 실행 단계가 필요
데코레이터 남용 시 디버깅이 어려워지는 포인트 3가지
결론: 데코레이터는 “보이지 않는 코드”를 만든다.
- 실행 순서 추적이 어렵다
- 정의 시점 실행 → 로그 위치가 예상과 다름
- 숨은 부작용
- prototype 변형, descriptor 변경이 코드 밖에서 발생
- 타입과 런타임 불일치
- 컴파일은 통과, 실행에서만 문제 발생
프레임워크가 데코레이터를 쓰는 구조적 이유
결론: 프레임워크는 “코드를 실행”하는 게 아니라 “구조를 해석”해야 한다.
Nest / Angular 관점
- 클래스 로드 시 메타데이터 수집
- DI 컨테이너, 라우팅, validation 규칙 구성
- 이후 런타임에서는 이미 만들어진 구조를 사용
핵심: 데코레이터 = 선언적 설정 + 정적 분석 가능한 구조 → 대규모 시스템에서 중앙 집중 제어 가능
실무 기준: 언제 쓰고, 언제 피하나?
결론: 프레임워크 경계에서는 쓰고, 비즈니스 로직에서는 피하라.
쓰는 경우
- DI, 라우팅, 권한, validation
- 프레임워크가 메타데이터를 소비할 때
피하는 경우
- 단순 유틸/비즈니스 로직
- 실행 순서가 중요한 코드
- 팀원이 데코레이터 모델에 익숙하지 않을 때
체크리스트
- 데코레이터 실행 시점을 정확히 이해하고 있는가?
- 데코레이터가 “로직”이 아니라 “정보”임을 인지했는가?
- 런타임 검증 로직이 별도로 존재하는가?
- prototype/descriptor 변경을 팀이 감당할 수 있는가?
- 프레임워크 외부 코드에 남용하고 있지 않은가?
요약 5줄
- 데코레이터는 인스턴스가 아니라 정의 시점에 실행된다.
- 데코레이터의 본질은 로직 실행이 아닌 메타데이터 수집이다.
- Decorator Factory는 옵션 전달을 위한 필수 구조다.
- validation/autobind는 구조적 문제를 해결하지만 런타임 검증은 별도다.
- 프레임워크 경계에서는 강력하지만, 남용하면 디버깅 비용이 폭증한다.
자기점검 질문 5개
- 데코레이터가 실행되는 정확한 시점을 설명할 수 있는가?
- 현재 사용 중인 데코레이터는 정보를 저장하는가, 로직을 숨기는가?
- 타입 안정성과 런타임 검증을 혼동하고 있지 않은가?
- 이 기능은 함수/설정 객체로 대체 가능하지 않은가?
- 팀원이 이 데코레이터 구조를 이해하지 못해도 유지보수 가능한가?
실전 미션 3개
- 기존 이벤트 핸들러의
bind코드를 autobind 데코레이터로 교체하고 비교하라. - validation 데코레이터 + validate 함수를 분리해 “정의/실행” 구조를 명확히 하라.
- 사용 중인 데코레이터 하나를 제거하고, 동일 기능을 함수/설정 기반으로 재구현해보라.
This post is licensed under CC BY 4.0 by the author.
