🎯 요약
@Controller
, @Injectable
같은 걸 보면서 "저건 대체 뭐지?" 하고 궁금했던 적 있나요? 저도 처음엔 그냥 마법의 주문 같았어요. 하지만 3년간 NestJS와 TypeORM으로 실무 프로젝트를 진행하면서 깨달은 건 데코레이터야말로 TypeScript의 진짜 숨겨진 보석이라는 거죠. 한번 제대로 익히면 반복 코드는 확 줄고 코드는 훨씬 깔끔해집니다.
📋 목차
- 데코레이터가 뭔지부터 차근차근
- 4가지 데코레이터 타입 완전 해부
- 실무에서 바로 쓰는 커스텀 데코레이터
- NestJS 스타일 고급 패턴 마스터
- 메타데이터와 reflect-metadata 활용법
- 실전 프로젝트에서 써먹는 고급 기법
데코레이터가 뭔지부터 차근차근
📍 데코레이터를 써야 하는 이유
코드를 작성하다 보면 이런 상황이 정말 자주 생기죠:
// 로그인 체크가 필요한 API들...
class UserController {
getUserProfile() {
// 매번 이런 코드를 반복해야 함 😱
if (!this.isAuthenticated()) {
throw new UnauthorizedException();
}
// 실제 로직...
}
updateUser() {
if (!this.isAuthenticated()) {
throw new UnauthorizedException();
}
// 또 반복...
}
deleteUser() {
if (!this.isAuthenticated()) {
throw new UnauthorizedException();
}
// 또또 반복...
}
}
매번 똑같은 인증 로직을 복사 붙여넣기 하는 게 정말 짜증나잖아요? 데코레이터로 해결하면:
class UserController {
@RequireAuth() // 이 한 줄로 끝!
getUserProfile() {
// 인증은 데코레이터가 알아서 처리
// 비즈니스 로직에만 집중
}
@RequireAuth()
updateUser() {
// 깔끔!
}
@RequireAuth()
deleteUser() {
// 완전 깔끔!
}
}
📊 제가 데코레이터 도입 후 체감한 변화:
- 보일러플레이트 코드 70% 감소
- 코드 가독성이 눈에 띄게 향상
- 비즈니스 로직과 부가 기능 완전 분리
- 재사용 가능한 기능들이 라이브러리처럼 쌓임
🚀 데코레이터의 진짜 정체
데코레이터는 사실 함수예요. 그것도 아주 특별한 함수죠. 클래스나 메서드가 정의될 때 자동으로 실행되면서 원래 코드를 감싸거나 수정할 수 있어요.
// 이렇게 쓰는 데코레이터는...
@MyDecorator()
class MyClass {}
// 사실 이런 함수 호출과 같아요
MyClass = MyDecorator()(MyClass);
신기하죠? 컴파일 타임에 코드를 조작하는 일종의 메타프로그래밍이거든요.
데코레이터 설정 (tsconfig.json):
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"target": "ES2020",
"lib": ["ES2020", "DOM"]
}
}
중요한 주의사항:
experimentalDecorators
: 현재 TypeScript 데코레이터 기능 활성화emitDecoratorMetadata
: reflect-metadata와 함께 사용 시 필수- Stage 3 proposal 데코레이터와는 문법이 다름 (현재 실험적 버전 사용 권장)
- 최신 tslib 설치 권장 (데코레이터 헬퍼 함수 지원)
4가지 데코레이터 타입 완전 해부
TypeScript에는 4가지 종류의 데코레이터가 있어요. 각각 언제 어떻게 쓰는지 실전 예제로 알아볼게요.
1. 클래스 데코레이터 - 클래스 전체를 조작
가장 많이 보는 패턴:
// NestJS의 @Controller 같은 패턴
function Controller(path: string) {
return function<T extends { new(...args: any[]): {} }>(constructor: T) {
// 클래스에 메타데이터 추가
Reflect.defineMetadata('path', path constructor);
// 원본 클래스를 확장해서 새로운 클래스 반환
return class extends constructor {
basePath = path;
constructor(...args: any[]) {
super(...args);
console.log(`Controller created with path: ${path}`);
}
};
};
}
// 사용법
@Controller('/users')
class UserController {
getUsers() {
return 'Getting users...';
}
}
const controller = new UserController();
// "Controller created with path: /users" 출력
2. 메서드 데코레이터 - 메서드를 감싸거나 수정
실무에서 가장 유용한 패턴:
// 실행 시간 측정 데코레이터
function Benchmark(target: any propertyKey: string descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = async function(...args: any[]) {
const start = Date.now();
try {
const result = await originalMethod.apply(this args);
const end = Date.now();
console.log(`${propertyKey} 실행 시간: ${end - start}ms`);
return result;
} catch (error) {
const end = Date.now();
console.log(`${propertyKey} 에러 발생 (${end - start}ms):`, error);
throw error;
}
};
}
// 캐싱 데코레이터
function Cache(ttl: number = 5000) {
const cache = new Map();
return function(target: any propertyKey: string descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function(...args: any[]) {
const key = JSON.stringify(args);
if (cache.has(key)) {
console.log(`캐시 히트: ${propertyKey}`);
return cache.get(key);
}
const result = originalMethod.apply(this args);
cache.set(key result);
// TTL 후 캐시 삭제
setTimeout(() => cache.delete(key), ttl);
return result;
};
};
}
class ApiService {
@Benchmark
@Cache(10000)
async fetchUserData(userId: number) {
// 실제로는 DB 조회나 API 호출
await new Promise(resolve => setTimeout(resolve 1000));
return { id: userId name: `User ${userId}` };
}
}
3. 프로퍼티 데코레이터 - 클래스 속성 관리
// 환경변수 자동 주입
function Env(key: string defaultValue?: string) {
return function(target: any propertyKey: string) {
// 프로퍼티 디스크립터 수정
const getter = () => {
return process.env[key] || defaultValue;
};
Object.defineProperty(target propertyKey, {
get: getter,
enumerable: true,
configurable: true
});
};
}
// 유효성 검사 데코레이터
function Required(target: any propertyKey: string) {
// 메타데이터에 필수 필드 정보 저장
const existingRequired = Reflect.getMetadata('required', target.constructor) || [];
Reflect.defineMetadata('required', [...existingRequired propertyKey], target.constructor);
}
class AppConfig {
@Env('DATABASE_URL', 'localhost:5432')
databaseUrl: string;
@Env('API_KEY')
@Required
apiKey: string;
@Env('PORT', '3000')
port: string;
}
// 유효성 검사 함수
function validateRequired<T>(obj: T): T {
const constructor = (obj as any).constructor;
const requiredFields = Reflect.getMetadata('required', constructor) || [];
for (const field of requiredFields) {
if (!(obj as any)[field]) {
throw new Error(`Required field '${field}' is missing`);
}
}
return obj;
}
4. 파라미터 데코레이터 - 메서드 파라미터 처리
// 유효성 검사 파라미터 데코레이터
function ValidateBody(target: any propertyKey: string parameterIndex: number) {
const existingParams = Reflect.getMetadata('validate-params', target propertyKey) || [];
existingParams.push({ index: parameterIndex type: 'body' });
Reflect.defineMetadata('validate-params', existingParams target propertyKey);
}
function ValidateParam(paramName: string) {
return function(target: any propertyKey: string parameterIndex: number) {
const existingParams = Reflect.getMetadata('validate-params', target propertyKey) || [];
existingParams.push({ index: parameterIndex type: 'param', name: paramName });
Reflect.defineMetadata('validate-params', existingParams target propertyKey);
};
}
// 실제 유효성 검사를 처리하는 메서드 데코레이터
function ValidateRequest(target: any propertyKey: string descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
const paramsToValidate = Reflect.getMetadata('validate-params', target propertyKey) || [];
descriptor.value = function(...args: any[]) {
// 파라미터 유효성 검사
for (const param of paramsToValidate) {
const value = args[param.index];
if (param.type === 'body' && (!value || Object.keys(value).length === 0)) {
throw new Error('Request body is required');
}
if (param.type === 'param' && !value) {
throw new Error(`Parameter '${param.name}' is required`);
}
}
return originalMethod.apply(this args);
};
}
class UserController {
@ValidateRequest
createUser(@ValidateBody userData: any) {
return `Creating user: ${JSON.stringify(userData)}`;
}
@ValidateRequest
getUser(@ValidateParam('id') userId: string) {
return `Getting user: ${userId}`;
}
}
실무에서 바로 쓰는 커스텀 데코레이터
실제 프로젝트에서 정말 유용했던 데코레이터들을 소개할게요.
1. API 응답 표준화 데코레이터
interface ApiResponse<T> {
success: boolean;
data?: T;
message: string;
timestamp: string;
}
function ApiResponseWrapper(message: string = 'Success') {
return function(target: any propertyKey: string descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = async function(...args: any[]) {
try {
const result = await originalMethod.apply(this args);
return {
success: true,
data: result,
message,
timestamp: new Date().toISOString()
} as ApiResponse<typeof result>;
} catch (error) {
return {
success: false,
message: error instanceof Error ? error.message : 'Unknown error',
timestamp: new Date().toISOString()
} as ApiResponse<null>;
}
};
};
}
class ProductController {
@ApiResponseWrapper('상품 목록을 성공적으로 조회했습니다')
async getProducts() {
// DB에서 상품 조회 로직
return [
{ id: 1 name: '상품 1' },
{ id: 2 name: '상품 2' }
];
}
@ApiResponseWrapper('상품이 성공적으로 생성되었습니다')
async createProduct(productData: any) {
// 상품 생성 로직
return { id: 3, ...productData };
}
}
2. 권한 체크 데코레이터
enum UserRole {
USER = 'user',
ADMIN = 'admin',
SUPER_ADMIN = 'super_admin'
}
function RequireRole(roles: UserRole[]) {
return function(target: any propertyKey: string descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function(...args: any[]) {
// 실제로는 JWT 토큰이나 세션에서 사용자 정보를 가져옴
const currentUser = this.getCurrentUser(); // 예시용 메서드
if (!currentUser) {
throw new Error('Authentication required');
}
if (!roles.includes(currentUser.role)) {
throw new Error(`Access denied. Required roles: ${roles.join(', ')}`);
}
return originalMethod.apply(this args);
};
};
}
class AdminController {
@RequireRole([UserRole.ADMIN UserRole.SUPER_ADMIN])
deleteUser(userId: string) {
return `User ${userId} deleted`;
}
@RequireRole([UserRole.SUPER_ADMIN])
deleteAllUsers() {
return 'All users deleted';
}
getCurrentUser() {
// 실제로는 JWT 디코딩이나 세션 조회
return { id: '1', role: UserRole.ADMIN };
}
}
3. 자동 로깅 데코레이터
enum LogLevel {
DEBUG = 'debug',
INFO = 'info',
WARN = 'warn',
ERROR = 'error'
}
function Log(level: LogLevel = LogLevel.INFO includeArgs: boolean = false) {
return function(target: any propertyKey: string descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
const className = target.constructor.name;
descriptor.value = async function(...args: any[]) {
const start = Date.now();
// 메서드 시작 로그
const logData = {
timestamp: new Date().toISOString(),
class: className,
method: propertyKey,
level,
...(includeArgs && { arguments: args })
};
console.log(`[${level.toUpperCase()}] ${className}.${propertyKey} started`, logData);
try {
const result = await originalMethod.apply(this args);
const duration = Date.now() - start;
// 성공 로그
console.log(`[${level.toUpperCase()}] ${className}.${propertyKey} completed (${duration}ms)`);
return result;
} catch (error) {
const duration = Date.now() - start;
// 에러 로그
console.error(`[ERROR] ${className}.${propertyKey} failed (${duration}ms):`, error);
throw error;
}
};
};
}
class OrderService {
@Log(LogLevel.INFO true)
async createOrder(orderData: any) {
// 주문 생성 로직
await new Promise(resolve => setTimeout(resolve 100)); // 시뮬레이션
return { orderId: '12345', ...orderData };
}
@Log(LogLevel.WARN)
async cancelOrder(orderId: string) {
// 주문 취소 로직
return `Order ${orderId} cancelled`;
}
}
NestJS 스타일 고급 패턴 마스터
NestJS에서 사용하는 데코레이터 패턴을 분석해보고 비슷한 기능을 직접 만들어볼게요.
의존성 주입 데코레이터 만들기
// 간단한 DI 컨테이너
class DIContainer {
private static services = new Map<string any>();
private static instances = new Map<string any>();
static register<T>(token: string service: new(...args: any[]) => T) {
this.services.set(token service);
}
static get<T>(token: string): T {
if (this.instances.has(token)) {
return this.instances.get(token);
}
const ServiceClass = this.services.get(token);
if (!ServiceClass) {
throw new Error(`Service ${token} not found`);
}
const instance = new ServiceClass();
this.instances.set(token instance);
return instance;
}
}
// @Injectable 데코레이터
function Injectable(token?: string) {
return function<T extends { new(...args: any[]): {} }>(constructor: T) {
const serviceToken = token || constructor.name;
DIContainer.register(serviceToken constructor);
return constructor;
};
}
// @Inject 데코레이터
function Inject(token: string) {
return function(target: any propertyKey: string) {
Object.defineProperty(target propertyKey, {
get() {
return DIContainer.get(token);
},
enumerable: true,
configurable: true
});
};
}
// 사용 예시
@Injectable('UserService')
class UserService {
getUser(id: string) {
return { id name: `User ${id}` };
}
}
@Injectable('EmailService')
class EmailService {
sendEmail(to: string subject: string) {
console.log(`Sending email to ${to}: ${subject}`);
}
}
class UserController {
@Inject('UserService')
private userService: UserService;
@Inject('EmailService')
private emailService: EmailService;
createUser(userData: any) {
const user = this.userService.getUser('123');
this.emailService.sendEmail(user.name, 'Welcome!');
return user;
}
}
라우팅 데코레이터 시스템
// 간단한 라우팅 시스템
interface Route {
method: string;
path: string;
handler: string;
middlewares: Function[];
}
class Router {
private static routes: Route[] = [];
static addRoute(route: Route) {
this.routes.push(route);
}
static getRoutes() {
return this.routes;
}
static printRoutes() {
console.log('Registered routes:');
this.routes.forEach(route => {
console.log(`${route.method.toUpperCase()} ${route.path} -> ${route.handler}`);
});
}
}
// HTTP 메서드 데코레이터들
function Get(path: string) {
return function(target: any propertyKey: string) {
Router.addRoute({
method: 'GET',
path,
handler: `${target.constructor.name}.${propertyKey}`,
middlewares: []
});
};
}
function Post(path: string) {
return function(target: any propertyKey: string) {
Router.addRoute({
method: 'POST',
path,
handler: `${target.constructor.name}.${propertyKey}`,
middlewares: []
});
};
}
// 미들웨어 데코레이터
function UseMiddleware(...middlewares: Function[]) {
return function(target: any propertyKey: string) {
const routes = Router.getRoutes();
const lastRoute = routes[routes.length - 1];
if (lastRoute && lastRoute.handler.includes(propertyKey)) {
lastRoute.middlewares.push(...middlewares);
}
};
}
// 인증 미들웨어 예시
const authMiddleware = (req: any res: any next: any) => {
console.log('Auth middleware executed');
next();
};
const logMiddleware = (req: any res: any next: any) => {
console.log('Log middleware executed');
next();
};
class ApiController {
@Get('/users')
@UseMiddleware(authMiddleware logMiddleware)
getUsers() {
return 'Getting users...';
}
@Post('/users')
@UseMiddleware(authMiddleware)
createUser() {
return 'Creating user...';
}
@Get('/health')
healthCheck() {
return 'OK';
}
}
// 라우트 등록 확인
const controller = new ApiController();
Router.printRoutes();
메타데이터와 reflect-metadata 활용법
더 고급 데코레이터를 만들려면 메타데이터 API를 잘 알아야 해요.
reflect-metadata 설치 및 설정
npm install reflect-metadata
npm install -D @types/reflect-metadata
# 최신 tslib도 함께 설치 (데코레이터 헬퍼 함수 지원)
npm install tslib
// main.ts 최상단에 import
import 'reflect-metadata';
// TypeScript 5.0+ 사용 시 추가 설정 필요없음
메타데이터 기반 유효성 검사 시스템
// 유효성 검사 데코레이터들
function IsRequired(target: any propertyKey: string) {
Reflect.defineMetadata('isRequired', true target propertyKey);
}
function IsString(target: any propertyKey: string) {
Reflect.defineMetadata('type', 'string', target propertyKey);
}
function IsNumber(target: any propertyKey: string) {
Reflect.defineMetadata('type', 'number', target propertyKey);
}
function MinLength(length: number) {
return function(target: any propertyKey: string) {
Reflect.defineMetadata('minLength', length target propertyKey);
};
}
function MaxLength(length: number) {
return function(target: any propertyKey: string) {
Reflect.defineMetadata('maxLength', length target propertyKey);
};
}
// 유효성 검사 클래스 정의
class CreateUserDTO {
@IsRequired
@IsString
@MinLength(2)
@MaxLength(50)
name: string;
@IsRequired
@IsString
@MinLength(5)
email: string;
@IsNumber
age?: number;
}
// 유효성 검사 함수
function validate<T>(obj: T): { isValid: boolean; errors: string[] } {
const errors: string[] = [];
const keys = Object.keys(obj as any);
for (const key of keys) {
const value = (obj as any)[key];
// Required 체크
if (Reflect.getMetadata('isRequired', obj key) && (value === undefined || value === null)) {
errors.push(`${key} is required`);
continue;
}
// 값이 없으면 다른 검사는 스킵
if (value === undefined || value === null) continue;
// 타입 체크
const expectedType = Reflect.getMetadata('type', obj key);
if (expectedType && typeof value !== expectedType) {
errors.push(`${key} must be ${expectedType}`);
}
// 문자열 길이 체크
if (typeof value === 'string') {
const minLength = Reflect.getMetadata('minLength', obj key);
if (minLength && value.length < minLength) {
errors.push(`${key} must be at least ${minLength} characters long`);
}
const maxLength = Reflect.getMetadata('maxLength', obj key);
if (maxLength && value.length > maxLength) {
errors.push(`${key} must be at most ${maxLength} characters long`);
}
}
}
return {
isValid: errors.length === 0,
errors
};
}
// Body 유효성 검사 데코레이터
function ValidateBody<T>(dtoClass: new() => T) {
return function(target: any propertyKey: string descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function(...args: any[]) {
const bodyData = args[0]; // 첫 번째 인자가 body라고 가정
// DTO 인스턴스 생성
const dto = Object.assign(new dtoClass(), bodyData);
// 유효성 검사
const validation = validate(dto);
if (!validation.isValid) {
throw new Error(`Validation failed: ${validation.errors.join(', ')}`);
}
// 검증된 DTO를 인자로 전달
args[0] = dto;
return originalMethod.apply(this args);
};
};
}
// 사용 예시
class UserController {
@ValidateBody(CreateUserDTO)
createUser(userData: CreateUserDTO) {
console.log('Creating user with validated data:', userData);
return { id: '123', ...userData };
}
}
// 테스트
const controller = new UserController();
try {
const result = controller.createUser({
name: 'John Doe',
email: 'john@example.com',
age: 30
} as any);
console.log('Success:', result);
} catch (error) {
console.error('Validation error:', error.message);
}
실전 프로젝트에서 써먹는 고급 기법
1. 데코레이터 합성(Composition)
여러 데코레이터를 조합해서 더 강력한 기능을 만들어보죠:
// 여러 데코레이터를 한 번에 적용하는 유틸리티
function compose(...decorators: any[]) {
return function(target: any propertyKey?: string descriptor?: PropertyDescriptor) {
decorators.reverse().forEach(decorator => {
if (typeof decorator === 'function') {
if (propertyKey && descriptor) {
decorator(target propertyKey descriptor);
} else {
decorator(target);
}
}
});
};
}
// 자주 사용하는 데코레이터 조합을 미리 정의
const SecureApiEndpoint = compose(
RequireRole([UserRole.ADMIN]),
Log(LogLevel.INFO true),
Benchmark,
ApiResponseWrapper('Operation completed successfully')
);
class SecureController {
@SecureApiEndpoint
sensitiveOperation(data: any) {
return `Processing sensitive data: ${JSON.stringify(data)}`;
}
}
2. 조건부 데코레이터
환경이나 설정에 따라 다르게 동작하는 데코레이터:
function ConditionalCache(condition: () => boolean ttl: number = 5000) {
return function(target: any propertyKey: string descriptor: PropertyDescriptor) {
if (!condition()) {
// 조건이 맞지 않으면 데코레이터를 적용하지 않음
return;
}
// 조건이 맞으면 캐시 데코레이터 적용
Cache(ttl)(target propertyKey descriptor);
};
}
function ConditionalLog(condition: () => boolean level: LogLevel = LogLevel.INFO) {
return function(target: any propertyKey: string descriptor: PropertyDescriptor) {
if (!condition()) {
return;
}
Log(level true)(target propertyKey descriptor);
};
}
class ProductService {
@ConditionalCache(() => process.env.NODE_ENV === 'production', 10000)
@ConditionalLog(() => process.env.NODE_ENV === 'development', LogLevel.DEBUG)
async getProducts() {
// 프로덕션에서는 캐시 적용 개발에서는 로깅 적용
return ['Product 1', 'Product 2'];
}
}
3. 타입 안전한 데코레이터 팩토리
// 타입 안전한 데코레이터 인터페이스
interface DecoratorOptions {
cache?: { ttl: number };
auth?: { roles: UserRole[] };
log?: { level: LogLevel; includeArgs: boolean };
validate?: { bodyType?: new() => any; paramTypes?: Record<string string> };
}
function ApiEndpoint<T extends DecoratorOptions>(options: T) {
return function(target: any propertyKey: string descriptor: PropertyDescriptor) {
// 옵션에 따라 다른 데코레이터들을 적용
if (options.cache) {
Cache(options.cache.ttl)(target propertyKey descriptor);
}
if (options.auth) {
RequireRole(options.auth.roles)(target propertyKey descriptor);
}
if (options.log) {
Log(options.log.level options.log.includeArgs)(target propertyKey descriptor);
}
if (options.validate?.bodyType) {
ValidateBody(options.validate.bodyType)(target propertyKey descriptor);
}
// 항상 응답을 래핑
ApiResponseWrapper()(target propertyKey descriptor);
};
}
// 타입 안전한 사용
class ModernController {
@ApiEndpoint({
auth: { roles: [UserRole.ADMIN] },
log: { level: LogLevel.INFO includeArgs: true },
cache: { ttl: 30000 }
})
getAdminData() {
return { adminData: 'sensitive information' };
}
@ApiEndpoint({
validate: { bodyType: CreateUserDTO },
log: { level: LogLevel.DEBUG includeArgs: false }
})
createUser(userData: CreateUserDTO) {
return { created: true user: userData };
}
}
💡 실무 활용 꿀팁
데코레이터 성능 최적화
// 메모이제이션을 활용한 데코레이터 최적화
const decoratorCache = new Map();
function MemoizedDecorator(key: string) {
return function(target: any propertyKey: string descriptor: PropertyDescriptor) {
const cacheKey = `${target.constructor.name}.${propertyKey}.${key}`;
if (decoratorCache.has(cacheKey)) {
descriptor.value = decoratorCache.get(cacheKey);
return;
}
const originalMethod = descriptor.value;
const memoizedMethod = function(...args: any[]) {
// 실제 로직
return originalMethod.apply(this args);
};
decoratorCache.set(cacheKey memoizedMethod);
descriptor.value = memoizedMethod;
};
}
에러 처리 전략
function SafeExecute(fallbackValue?: any) {
return function(target: any propertyKey: string descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = async function(...args: any[]) {
try {
return await originalMethod.apply(this args);
} catch (error) {
console.error(`Error in ${target.constructor.name}.${propertyKey}:`, error);
// 에러 로깅 모니터링 등
if (typeof window !== 'undefined' && (window as any).Sentry) {
(window as any).Sentry.captureException(error);
}
return fallbackValue;
}
};
};
}
class RobustService {
@SafeExecute([])
async getDataWithFallback() {
// 실패할 수 있는 로직
throw new Error('Network error');
}
}
자주 묻는 질문 (FAQ)
Q1: 데코레이터는 언제 실행되나요?
A: 클래스가 정의되는 시점(컴파일 타임)에 실행돼요. 런타임이 아닌 클래스 선언 시에 바로 동작합니다.
Q2: 데코레이터가 성능에 영향을 주나요?
A: 데코레이터 자체는 컴파일 타임에 실행되므로 런타임 성능에는 크게 영향 없어요. 내부 로직에 따라 차이가 있을 수 있지만요.
Q3: 여러 데코레이터를 함께 사용할 때 실행 순서는?
A: 아래에서 위로(bottom-up) 실행됩니다. @A @B method()
에서 B가 먼저 A가 나중에 실행돼요.
Q4: 프로덕션에서 사용해도 안전한가요?
A: NestJS 같은 프레임워워크에서 널리 사용되고 있어서 안정적이에요. 다만 아직 실험적 기능이라 주의는 필요해요.
Q5: 데코레이터 없이는 같은 기능을 구현할 수 없나요?
A: 구현할 수는 있지만 코드가 복잡하고 재사용성이 떨어져요. 데코레이터의 진짜 가치는 깔끔함과 재사용성이거든요.
❓ TypeScript 데코레이터 마스터 마무리
데코레이터는 처음에는 마법 같아 보이지만 원리를 이해하면 정말 강력한 도구가 돼요. 특히 반복되는 코드를 줄이고 비즈니스 로직에 집중할 수 있게 해주는 게 가장 큰 장점이에요.
실무에서 써보면 코드가 훨씬 깔끔해지고 유지보수도 쉬워집니다. NestJS나 Angular 같은 프레임워크를 쓸 때도 이제 @Controller
, @Injectable
같은 게 왜 있는지 이해가 되실 거예요!
데코레이터로 메타프로그래밍의 세계를 경험해보세요. 코드가 코드를 만드는 재미를 느낄 수 있을 거예요! 💪
🔗 TypeScript 심화 학습 시리즈
데코레이터를 마스터하셨다면 다른 고급 TypeScript 기능들도 함께 학습해보세요:
📚 다음 단계 학습 가이드
- TypeScript 조건부 타입 완전정복: 고급 타입 추론과 조건부 로직 마스터하기
- TypeScript 함수 오버로딩 완전 가이드: 복잡한 함수 시그니처를 깔끔하게 정의하기
- TypeScript 제네릭 마스터 가이드: 재사용 가능한 타입 안전 코드 작성하기
- React + TypeScript 실무 패턴: 컴포넌트와 데코레이터 패턴 결합하기
- TypeScript 에러 디버깅 완전 가이드: 데코레이터 관련 에러 해결하기