본문으로 건너뛰기
Previous
Next
TypeScript 5 완벽 가이드 | Decorators·const Type Parameters

TypeScript 5 완벽 가이드 | Decorators·const Type Parameters

TypeScript 5 완벽 가이드 | Decorators·const Type Parameters

이 글의 핵심

TypeScript 5의 새로운 기능 완벽 가이드. Decorators, const Type Parameters, satisfies 연산자, 성능 개선까지 실전 예제로 정리. TypeScript·TypeScript 5·JavaScript 중심으로 설명합니다.

이 글의 핵심

TypeScript 5의 새로운 기능을 실전 예제로 완벽 정리합니다. Decorators, const Type Parameters, satisfies 연산자, 성능 개선까지 실무에 바로 적용할 수 있는 가이드입니다.

실무 경험 공유: 대규모 웹 애플리케이션을 TypeScript 5로 마이그레이션하면서, Decorators로 보일러플레이트 코드를 40% 줄이고 빌드 시간을 30% 단축한 경험을 공유합니다.

들어가며: “TypeScript 5, 뭐가 달라졌나요?”

실무 문제 시나리오

시나리오 1: 데코레이터가 표준이 아니었어요

TypeScript 4에서는 실험적 기능이었습니다. TypeScript 5는 ECMAScript 표준 Decorators를 지원합니다. 시나리오 2: 타입 추론이 부족해요

복잡한 객체의 타입을 정확히 추론하지 못했습니다. satisfies 연산자로 해결됩니다. 시나리오 3: 빌드가 느려요

대형 프로젝트에서 빌드가 2분 걸렸습니다. TypeScript 5는 30% 더 빠릅니다.

flowchart LR
    subgraph TS4[TypeScript 4]
        A1[실험적 Decorators]
        A2[제한적 타입 추론]
        A3[빌드: 2분]
    end
    subgraph TS5[TypeScript 5]
        B1[표준 Decorators]
        B2[satisfies 연산자]
        B3[빌드: 1분 24초]
    end
    TS4 --> TS5

1. Decorators (표준)

기본 사용법

// decorators.ts
// 클래스 데코레이터
function logged(value: Function, context: ClassDecoratorContext) {
  const className = context.name;
  
  return class extends value {
    constructor(...args: any[]) {
      super(...args);
      console.log(`[${className}] 인스턴스 생성됨`);
    }
  };
}
@logged
class User {
  constructor(public name: string) {}
}
const user = new User('John');
// 출력: [User] 인스턴스 생성됨

메서드 데코레이터

// method-decorator.ts
function measure(
  target: Function,
  context: ClassMethodDecoratorContext
) {
  const methodName = String(context.name);
  
  return function (this: any, ...args: any[]) {
    const start = performance.now();
    const result = target.apply(this, args);
    const end = performance.now();
    
    console.log(`[${methodName}] 실행 시간: ${end - start}ms`);
    return result;
  };
}
class Calculator {
  @measure
  heavyCalculation(n: number): number {
    let sum = 0;
    for (let i = 0; i < n; i++) {
      sum += i;
    }
    return sum;
  }
}
const calc = new Calculator();
calc.heavyCalculation(1000000);
// 출력: [heavyCalculation] 실행 시간: 5.2ms

실전 예제: API 라우터

// api-router.ts
const routes = new Map<string, Function>();
function Route(path: string) {
  return function (
    target: Function,
    context: ClassMethodDecoratorContext
  ) {
    routes.set(path, target);
    return target;
  };
}
class ApiController {
  @Route('/api/users')
  getUsers() {
    return [
      { id: 1, name: 'John' },
      { id: 2, name: 'Jane' },
    ];
  }
  @Route('/api/posts')
  getPosts() {
    return [
      { id: 1, title: 'Hello' },
      { id: 2, title: 'World' },
    ];
  }
}
// 라우팅
function handleRequest(path: string) {
  const handler = routes.get(path);
  if (handler) {
    return handler();
  }
  return { error: 'Not Found' };
}
console.log(handleRequest('/api/users'));
// [{ id: 1, name: 'John' }, { id: 2, name: 'Jane' }]

2. const Type Parameters

문제 상황

// TypeScript 4
function makeArray<T>(items: T[]) {
  return items;
}
const arr = makeArray([1, 2, 3]);
// 타입: number[]
// 원하는 타입: [1, 2, 3] (튜플)

해결: const Type Parameters

// TypeScript 5
function makeArray<const T>(items: T[]) {
  return items;
}
const arr = makeArray([1, 2, 3]);
// 타입: readonly [1, 2, 3]

실전 예제: 라우트 정의

// routes.ts
function defineRoutes<const T extends Record<string, string>>(routes: T) {
  return routes;
}
const routes = defineRoutes({
  home: '/',
  about: '/about',
  contact: '/contact',
} as const);
// 타입: { readonly home: "/"; readonly about: "/about"; readonly contact: "/contact"; }
// 자동완성 지원
type RouteName = keyof typeof routes;  // "home" | "about" | "contact"
function navigate(route: RouteName) {
  window.location.href = routes[route];
}
navigate('home');  // ✅
navigate('invalid');  // ❌ 타입 에러

3. satisfies 연산자

문제 상황

// TypeScript 4
type Color = 'red' | 'green' | 'blue' | [number, number, number];
const palette: Record<string, Color> = {
  primary: 'red',
  secondary: [0, 255, 0],
};
// 문제: palette.primary.toUpperCase() 불가능
// palette의 타입이 Record<string, Color>로 고정되어
// 'red'가 string 리터럴이 아닌 Color 타입으로 추론됨

해결: satisfies

// TypeScript 5
type Color = 'red' | 'green' | 'blue' | [number, number, number];
const palette = {
  primary: 'red',
  secondary: [0, 255, 0],
} satisfies Record<string, Color>;
// ✅ 타입 체크는 통과하면서도 정확한 타입 추론
palette.primary.toUpperCase();  // ✅ 'red'는 string 리터럴
palette.secondary[0];  // ✅ [number, number, number]

실전 예제: 설정 객체

// config.ts
type Environment = 'development' | 'staging' | 'production';
interface Config {
  env: Environment;
  apiUrl: string;
  features: Record<string, boolean>;
}
const config = {
  env: 'production',
  apiUrl: 'https://api.example.com',
  features: {
    darkMode: true,
    analytics: true,
    beta: false,
  },
} satisfies Config;
// ✅ 타입 체크 통과 + 정확한 추론
if (config.env === 'production') {  // 'production' 리터럴
  console.log('프로덕션 모드');
}
config.features.darkMode;  // boolean

4. 성능 개선

빌드 속도

# TypeScript 4.9
tsc --build  # 120초
# TypeScript 5.0
tsc --build  # 84초 (30% 개선)

모듈 해상도 최적화

// tsconfig.json
{
  "compilerOptions": {
    "moduleResolution": "bundler",  // 새로운 옵션
    "allowImportingTsExtensions": true,
    "noEmit": true
  }
}

5. 기타 새로운 기능

1. 모든 Enum은 Union Enum

// TypeScript 5
enum Status {
  Pending = 'pending',
  Success = 'success',
  Error = 'error',
}
// 자동으로 Union 타입처럼 동작
type StatusValue = `${Status}`;  // "pending" | "success" | "error"

2. export type * 지원

// types.ts
export type User = { id: number; name: string };
export type Post = { id: number; title: string };
// index.ts
export type * from './types';  // 모든 타입만 export

3. 향상된 switch(true) 타입 좁히기

function process(value: string | number) {
  switch (true) {
    case typeof value === 'string':
      // TypeScript 5: value는 string으로 좁혀짐
      return value.toUpperCase();
    
    case typeof value === 'number':
      // value는 number로 좁혀짐
      return value.toFixed(2);
  }
}

6. 마이그레이션 가이드

1. TypeScript 업그레이드

npm install -D typescript@latest

2. tsconfig.json 업데이트

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "experimentalDecorators": false,  // 표준 Decorators 사용
    "strict": true,
    "skipLibCheck": true,
    "esModuleInterop": true
  }
}

3. Decorators 마이그레이션

// TypeScript 4 (실험적)
function OldDecorator(target: any) {
  // ...
}
// TypeScript 5 (표준)
function NewDecorator(value: Function, context: ClassDecoratorContext) {
  // context.name, context.kind 사용 가능
}

4. 빌드 및 테스트

# 타입 체크
tsc --noEmit
# 빌드
npm run build
# 테스트
npm test

7. 실전 예제: NestJS 스타일 컨트롤러

// controller.ts
const routes = new Map<string, Map<string, Function>>();
function Controller(prefix: string) {
  return function (value: Function, context: ClassDecoratorContext) {
    routes.set(prefix, new Map());
    return value;
  };
}
function Get(path: string) {
  return function (
    target: Function,
    context: ClassMethodDecoratorContext
  ) {
    const className = context.name;
    // 라우트 등록 로직
    return target;
  };
}
function Post(path: string) {
  return function (
    target: Function,
    context: ClassMethodDecoratorContext
  ) {
    // 라우트 등록 로직
    return target;
  };
}
@Controller('/api/users')
class UserController {
  @Get('/')
  getUsers() {
    return [
      { id: 1, name: 'John' },
      { id: 2, name: 'Jane' },
    ];
  }
  @Post('/')
  createUser(data: { name: string }) {
    return { id: 3, name: data.name };
  }
  @Get('/:id')
  getUser(id: string) {
    return { id, name: 'John' };
  }
}

정리 및 체크리스트

핵심 요약

  • Decorators: ECMAScript 표준 Decorators 지원
  • const Type Parameters: 더 정확한 타입 추론
  • satisfies 연산자: 타입 체크 + 정확한 추론
  • 성능: 빌드 속도 30% 향상
  • 호환성: TypeScript 4 코드 대부분 호환

마이그레이션 체크리스트

  • TypeScript 5 설치
  • tsconfig.json 업데이트
  • experimentalDecorators 제거 (표준 사용 시)
  • 타입 에러 수정
  • 빌드 테스트
  • 프로덕션 배포

같이 보면 좋은 글


이 글에서 다루는 키워드

TypeScript, TypeScript 5, Decorators, satisfies, const Type Parameters, 타입 안전성

자주 묻는 질문 (FAQ)

Q. TypeScript 5로 업그레이드해야 하나요?

A. 새 프로젝트는 TypeScript 5를 권장합니다. 기존 프로젝트는 대부분 호환되므로 점진적 업그레이드가 가능합니다.

Q. Decorators를 사용하려면 어떻게 하나요?

A. tsconfig.json에서 experimentalDecorators: false로 설정하면 표준 Decorators를 사용할 수 있습니다. 기존 실험적 Decorators와는 문법이 다릅니다.

Q. satisfies와 as의 차이는?

A. as는 타입을 강제로 변환합니다. satisfies는 타입 체크만 하고 원래 타입을 유지합니다. 더 안전하고 정확한 타입 추론이 가능합니다.

Q. 성능이 얼마나 개선되었나요?

A. 빌드 속도는 평균 30% 향상되었습니다. 대형 프로젝트일수록 개선 폭이 큽니다.

심화 부록: 구현·운영 관점

이 부록은 앞선 본문에서 다룬 주제(「TypeScript 5 완벽 가이드 | Decorators·const Type Parameters·satisfies 연산자」)를 구현·런타임·운영 관점에서 다시 압축합니다. 도메인별 세부 구현은 글마다 다르지만, 입력 검증 → 핵심 연산 → 부작용(I/O·네트워크·동시성) → 관측의 흐름으로 장애를 나누면 원인 추적이 빨라집니다.

내부 동작과 핵심 메커니즘

flowchart TD
  A[입력·요청·이벤트] --> B[파싱·검증·디코딩]
  B --> C[핵심 연산·상태 전이]
  C --> D[부작용: I/O·네트워크·동시성]
  D --> E[결과·관측·저장]
sequenceDiagram
  participant C as 클라이언트/호출자
  participant B as 경계(런타임·게이트웨이·프로세스)
  participant D as 의존성(API·DB·큐·파일)
  C->>B: 요청/이벤트
  B->>D: 조회·쓰기·RPC
  D-->>B: 지연·부분 실패·재시도 가능
  B-->>C: 응답 또는 오류(코드·상관 ID)
  • 불변 조건(Invariant): 버퍼 경계, 프로토콜 상태, 트랜잭션 격리, FD 상한 등 단계별로 문장으로 적어 두면 디버깅 비용이 줄어듭니다.
  • 결정성: 순수 층과 시간·네트워크·스케줄에 의존하는 층을 분리해야 테스트와 장애 분석이 쉬워집니다.
  • 경계 비용: 직렬화, 인코딩, syscall 횟수, 락 경합, 할당·GC, 캐시 미스를 의심 목록에 둡니다.
  • 백프레셔: 생산자가 소비자보다 빠를 때 버퍼·큐·스트림에서 속도를 줄이는 신호를 어디에 둘지 정의합니다.

프로덕션 운영 패턴

영역운영 관점 질문
관측성요청 단위 상관 ID, 에러율·지연 p95/p99, 의존성 타임아웃·재시도가 대시보드에 보이는가
안전성입력 검증·권한·비밀·감사 로그가 코드 경로마다 일관적인가
신뢰성재시도는 멱등 연산에만 적용되는가, 서킷 브레이커·백오프·DLQ가 있는가
성능캐시·배치 크기·커넥션 풀·인덱스·백프레셔가 데이터 규모에 맞는가
배포롤백 룬북, 카나리/블루그린, 마이그레이션·피처 플래그가 문서화되어 있는가
용량피크 트래픽·디스크·FD·스레드 풀 상한을 주기적으로 검증하는가

스테이징은 데이터 양·네트워크 RTT·동시성을 프로덕션에 가깝게 맞출수록 재현율이 올라갑니다.

확장 예시: 엔드투엔드 미니 시나리오

앞선 본문 주제(「TypeScript 5 완벽 가이드 | Decorators·const Type Parameters·satisfies 연산자」)를 배포·운영 흐름에 맞춰 옮긴 체크리스트입니다. 도메인에 맞게 단계 이름만 바꿔 적용할 수 있습니다.

  1. 입력 계약 고정: 스키마·버전·최대 페이로드·타임아웃·에러 코드를 경계에 둔다.
  2. 핵심 경로 계측: 요청 ID, 단계별 지연, 외부 호출 결과 코드를 로그·메트릭·트레이스에서 한 흐름으로 본다.
  3. 실패 주입: 의존성 타임아웃·5xx·부분 데이터·락 대기를 스테이징에서 재현한다.
  4. 호환·롤백: 설정/마이그레이션/클라이언트 버전을 되돌릴 수 있는지 확인한다.
  5. 부하 후 검증: 피크 대비 p95/p99, 에러율, 리소스 상한, 알림 임계값을 점검한다.
handle(request):
  ctx = newCorrelationId()
  validated = validateSchema(request)
  authorize(validated, ctx)
  result = domainCore(validated)
  persistOrEmit(result, idempotentKey)
  recordMetrics(ctx, latency, outcome)
  return result

문제 해결(Troubleshooting)

증상가능 원인조치
간헐적 실패레이스, 타임아웃, 외부 의존성, DNS최소 재현 스크립트, 분산 트레이스·로그 상관관계, 재시도·서킷 설정 점검
성능 저하N+1, 동기 I/O, 락 경합, 과도한 직렬화, 캐시 미스프로파일러·APM으로 핫스팟 확인 후 한 가지씩 제거
메모리 증가캐시 무제한, 구독/리스너 누수, 대용량 버퍼, 커넥션 미반납상한·TTL·힙/FD 스냅샷 비교
빌드·배포만 실패환경 변수, 권한, 플랫폼 차이, lockfileCI 로그와 로컬 diff, 런타임·이미지 버전 핀
설정 불일치프로필·시크릿·기본값, 리전스키마 검증된 설정 단일 소스와 배포 매트릭스 표준화
데이터 불일치비멱등 재시도, 부분 쓰기, 캐시 무효화 누락멱등 키·아웃박스·트랜잭션 경계 재검토

권장 순서: (1) 최소 재현 (2) 최근 변경 범위 축소 (3) 환경·의존성 차이 (4) 관측으로 가설 검증 (5) 수정 후 회귀·부하 테스트.

배포 전에는 git addgit commitgit pushnpm run deploy 순서를 권장합니다.