IT· 17분 읽기

TypeScript 5 새 기능 — satisfies 연산자와 데코레이터 실전 활용

TypeScript 5의 핵심 신기능 완벽 가이드. satisfies 연산자로 타입 안전성 강화, 새로운 데코레이터 표준 구현, const 타입 파라미터, 다중 설정 파일 확장 등 실무에서 바로 쓸 수 있는 코드 예시 포함.

핵심 요약 TypeScript 5.0~5.4의 주요 신기능: ① satisfies 연산자 — 추론 유지하면서 타입 검증 ② 표준 데코레이터 — TC39 Stage 3 표준 준수, 메타데이터 지원 ③ const 타입 파라미터 — 리터럴 타입 추론 강화 ④ 다중 tsconfig 확장 ⑤ enum·namespace 개선. 실무에서 가장 파급력 큰 기능은 satisfies와 새 데코레이터.

TypeScript 5 개요

TypeScript 5는 2023년 3월부터 시작된 주요 버전으로, 5.0~5.4까지 여러 마이너 업데이트가 누적되어 있습니다. 이 버전에서 가장 중요한 변화는 표준 데코레이터satisfies 연산자의 도입입니다.

버전별 주요 출시 일정:

버전출시주요 기능
TypeScript 5.02023-03satisfies, const 타입 파라미터, 다중 tsconfig 확장
TypeScript 5.12023-06getter/setter 독립 타입, JSX 개선
TypeScript 5.22023-08using/await using 키워드, 데코레이터 메타데이터
TypeScript 5.32023-11Import Attributes, 타입 좁히기 개선
TypeScript 5.42024-03NoInfer 유틸리티, 클로저 타입 좁히기 보존

1. satisfies 연산자 — 실전 완전 정복

satisfies 연산자는 TypeScript 5.0에서 도입된 가장 혁신적인 기능 중 하나입니다. 기존 타입 단언의 한계를 넘어 타입 추론을 유지하면서 타입 제약을 검증합니다.

기존 문제점

typescript
// ❌ 기존 방식의 문제
type Color = "red" | "green" | "blue";
type ColorMap = Record<string, string | number[]>;

// as 단언: 타입 검증 O, 추론 X
const palette = {
  red: [255, 0, 0],
  green: "#00ff00",
  blue: [0, 0, 255]
} as ColorMap;

// 이제 palette.red는 string | number[] 로 추론됨
// .map()을 쓰려면 타입 단언 필요
palette.red.map(x => x); // 오류! string | number[]에 map 없음

satisfies 활용

typescript
// ✅ satisfies 연산자
type Color = "red" | "green" | "blue";
type ColorMap = Record<Color, string | number[]>;

const palette = {
  red: [255, 0, 0],
  green: "#00ff00",
  blue: [0, 0, 255]
} satisfies ColorMap;

// 이제 타입 검증 O, 리터럴 추론 O
palette.red.map(x => x);      // ✅ number[] 유지됨
palette.green.toUpperCase();   // ✅ string 유지됨
const wrong = { purple: "purple" } satisfies ColorMap; // ❌ 오류: "purple"은 Color 아님

실전 활용 패턴

패턴 1: API 응답 객체 검증

typescript
interface ApiConfig {
  baseUrl: string;
  timeout: number;
  retries: number;
}

// satisfies로 타입 검증 + 정확한 값 추론 유지
const config = {
  baseUrl: "https://api.example.com",
  timeout: 5000,
  retries: 3,
} satisfies ApiConfig;

// config.timeout은 number가 아닌 5000 (리터럴 타입)
type TimeoutType = typeof config.timeout; // 5000

패턴 2: 라우트 정의

typescript
type Route = {
  path: string;
  component: React.ComponentType;
  auth?: boolean;
};

const routes = [
  { path: "/", component: Home },
  { path: "/dashboard", component: Dashboard, auth: true },
] satisfies Route[];

// routes[0].path는 "/" (리터럴) 유지, Route 검증도 통과

패턴 3: 다국어 번역 객체

typescript
type TranslationKey = "hello" | "goodbye" | "welcome";
type Translations = Record<TranslationKey, string>;

const ko = {
  hello: "안녕하세요",
  goodbye: "안녕히 가세요",
  welcome: "환영합니다",
} satisfies Translations;

const en = {
  hello: "Hello",
  goodbye: "Goodbye",
  // welcome 누락 → 컴파일 오류 발생! ✅
} satisfies Translations;

2. 표준 데코레이터 — TC39 Stage 3 완전 구현

TypeScript 5.0은 오랫동안 사용하던 실험적 데코레이터(experimentalDecorators)와 별개로 TC39 Stage 3 표준 데코레이터를 지원합니다.

핵심 차이점

구분실험적 데코레이터표준 데코레이터 (5.0+)
활성화 방법experimentalDecorators: true기본 활성화 (tsconfig 불필요)
표준 준수자체 구현TC39 Stage 3
실행 순서역순정순
메타데이터emitDecoratorMetadataSymbol.metadata 사용
함수 지원제한적클래스 전용

클래스 데코레이터

typescript
// ✅ TypeScript 5 표준 데코레이터
function sealed(target: typeof SealedClass) {
  Object.seal(target);
  Object.seal(target.prototype);
}

@sealed
class SealedClass {
  greet() { return "Hello!"; }
}

메서드 데코레이터

typescript
function log(target: any, context: ClassMethodDecoratorContext) {
  const methodName = String(context.name);

  return function (this: any, ...args: any[]) {
    console.log(`[${methodName}] 호출됨, 인수:`, args);
    const result = target.call(this, ...args);
    console.log(`[${methodName}] 반환값:`, result);
    return result;
  };
}

class Calculator {
  @log
  add(a: number, b: number): number {
    return a + b;
  }
}

const calc = new Calculator();
calc.add(1, 2);
// [add] 호출됨, 인수: [1, 2]
// [add] 반환값: 3

접근자 데코레이터

typescript
function readonly(target: any, context: ClassAccessorDecoratorContext) {
  return {
    set() {
      throw new Error(`${String(context.name)}은 읽기 전용입니다.`);
    }
  };
}

class Config {
  @readonly
  accessor version = "1.0.0";
}

const cfg = new Config();
console.log(cfg.version); // "1.0.0"
cfg.version = "2.0.0";    // ❌ 오류 발생

데코레이터 메타데이터 (TypeScript 5.2)

typescript
// 메타데이터 API 활용
function validate(target: any, context: ClassFieldDecoratorContext) {
  context.metadata[context.name] = { required: true };
}

class UserForm {
  @validate
  name!: string;

  @validate
  email!: string;
}

// 메타데이터 읽기
const metadata = UserForm[Symbol.metadata];
console.log(metadata); // { name: { required: true }, email: { required: true } }

3. const 타입 파라미터

TypeScript 5.0에서 추가된 const 수정자는 제네릭 함수의 타입 추론을 강화합니다.

typescript
// ❌ 기존 방식
function identity<T>(value: T): T {
  return value;
}

const result = identity(["a", "b", "c"]);
// result: string[] (리터럴 손실)

// ✅ const 타입 파라미터
function identityConst<const T>(value: T): T {
  return value;
}

const result2 = identityConst(["a", "b", "c"]);
// result2: readonly ["a", "b", "c"] (리터럴 유지!)

실전 활용: 경로 빌더

typescript
function createRoute<const Path extends string>(path: Path) {
  return { path, url: () => `https://app.example.com${path}` };
}

const homeRoute = createRoute("/");
// homeRoute.path: "/" (리터럴 유지)

const dashRoute = createRoute("/dashboard");
// dashRoute.path: "/dashboard" (리터럴 유지)

4. 다중 tsconfig 확장

TypeScript 5.0부터 tsconfig.jsonextends에 배열을 사용할 수 있습니다.

json
// tsconfig.json
{
  "extends": [
    "@tsconfig/strictest/tsconfig.json",
    "@tsconfig/node18/tsconfig.json",
    "./custom.tsconfig.json"
  ],
  "compilerOptions": {
    "outDir": "dist"
  }
}

이전에는 체인 방식으로 여러 파일을 상속해야 했지만, 이제 배열로 한 번에 다중 상속이 가능합니다.

5. using / await using 키워드 (TypeScript 5.2)

자원 관리를 위한 새 키워드로, 자바의 try-with-resources와 유사합니다.

typescript
// Symbol.dispose가 있는 객체
class DbConnection {
  constructor(public readonly id: string) {}

  [Symbol.dispose]() {
    console.log(`연결 ${this.id} 종료`);
  }
}

function getConnection() {
  return new DbConnection("conn-001");
}

// using: 블록 탈출 시 자동으로 dispose() 호출
{
  using conn = getConnection();
  console.log(`연결 ${conn.id} 사용 중`);
  // 블록 종료 시 자동으로 "연결 conn-001 종료" 출력
}

// await using: 비동기 자원 해제
async function processFile() {
  await using file = await openFile("data.txt");
  // 처리 완료 후 자동으로 await file[Symbol.asyncDispose]() 호출
}

6. NoInfer 유틸리티 타입 (TypeScript 5.4)

특정 위치의 타입 추론을 의도적으로 차단합니다.

typescript
// 문제: T가 기본값으로도 추론되어 타입이 넓어짐
function createState<T>(initial: T, fallback: T): T {
  return initial ?? fallback;
}

// fallback이 초기값에 영향을 주면 안 될 때
const state = createState("active", "inactive");
// T: "active" | "inactive" (원하지 않을 수 있음)

// ✅ NoInfer 사용
function createStateBetter<T>(initial: T, fallback: NoInfer<T>): T {
  return initial ?? fallback;
}

const state2 = createStateBetter("active", "inactive");
// T: "active", fallback은 "active" 타입으로 검증됨
// createStateBetter("active", "unknown") // 오류!

마이그레이션 가이드

TypeScript 4.x → 5.x 전환 시 주의사항

변경 사항영향조치
실험적 데코레이터 유지기존 코드 호환experimentalDecorators: true 유지 가능
enum 상수 인라인 최적화빌드 결과물 변경 가능번들 크기 확인
tsconfig extends 배열기존 문자열 방식 호환변경 불필요
--verbatimModuleSyntax일부 import 오류타입 전용 import 명시 필요

권장 tsconfig 설정 (2026 기준)

json
{
  "compilerOptions": {
    "target": "ES2022",
    "lib": ["ES2022", "DOM", "DOM.Iterable"],
    "module": "Node16",
    "moduleResolution": "Node16",
    "strict": true,
    "exactOptionalPropertyTypes": true,
    "noUncheckedIndexedAccess": true,
    "noImplicitOverride": true,
    "verbatimModuleSyntax": true
  }
}

💡 TypeScript 코드 작성 도구가 필요하신가요? 코드 가독성 분석에는 읽기 시간 계산기를 활용해보세요.


📣 대가성 안내: 이 포스팅에는 일부 도구 링크가 포함되어 있으며, 소정의 수수료를 받을 수 있습니다.


자주 묻는 질문 (FAQ)

Q1. TypeScript 5는 TypeScript 4.x와 호환되나요? A. 대부분 하위 호환됩니다. 단, 실험적 데코레이터를 사용 중이라면 experimentalDecorators: true 설정 유지가 필요합니다.

Q2. satisfies와 as 단언의 차이는? A. as는 타입 검증 없이 강제 변환합니다. satisfies는 타입 제약을 검증하면서도 원래 리터럴 추론을 유지합니다.

Q3. 표준 데코레이터로 NestJS를 사용할 수 있나요? A. NestJS는 현재 experimentalDecorators: true 기반입니다. 표준 데코레이터로의 전환은 NestJS 팀이 장기 로드맵으로 계획 중입니다.

Q4. const 타입 파라미터는 언제 사용하면 좋나요? A. 함수가 리터럴 값을 그대로 반환하거나 튜플/배열의 정확한 타입이 필요할 때 유용합니다. 라우팅, 설정 객체, 번역 키 등에 적합합니다.

Q5. using 키워드를 지원하는 Node.js 버전은? A. using은 TypeScript 5.2+와 Node.js 18+ 조합에서 사용 가능합니다. 타입스크립트가 Symbol.dispose를 활용하므로 폴리필이 필요할 수 있습니다.

Q6. NoInfer는 실무에서 어떤 경우에 쓰나요? A. API 함수의 타입 파라미터가 기본값에 의해 의도치 않게 넓어지는 것을 방지할 때 유용합니다. 특히 제네릭 팩토리 함수 작성 시 활용됩니다.

Q7. TypeScript 5.4에서 클로저 타입 좁히기란? A. 기존에는 함수 내부 클로저에서 타입 좁히기가 유지되지 않는 경우가 있었습니다. 5.4부터 클로저 내에서도 마지막 할당이 타입 좁히기에 사용됩니다.

Q8. verbatimModuleSyntax 옵션이 무엇인가요? A. 타입 전용 import/export를 import typeexport type으로 명시적으로 구분하도록 강제합니다. 번들러와의 호환성을 높이고 불필요한 런타임 코드를 제거합니다.

🔧 이 글과 관련된 무료 도구

이 글과 관련된 상품 (TypeScript5)[광고/제휴]

이 포스팅은 쿠팡 파트너스, 아마존 어소시에이트, 알리익스프레스 제휴 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다. 이는 상품 가격에 영향을 주지 않습니다.
As an Amazon Associate, Coupang Partner, and AliExpress affiliate, I earn from qualifying purchases at no extra cost to you.

관련 글