본문으로 건너뛰기 TypeScript 5 Complete Guide | Decorators· satisfies

TypeScript 5 Complete Guide | Decorators· satisfies

TypeScript 5 Complete Guide | Decorators· satisfies

이 글의 핵심

TypeScript 5 brings standard Decorators, the satisfies operator, const type parameters, and a 30% build speed improvement. This guide covers each feature with real-world examples you can apply immediately.

What Changed in TypeScript 5

TypeScript 5 (released March 2023) is a landmark release with three major language features and a significant performance improvement:

  1. Standard Decorators — ECMAScript-stage Decorators replace the decade-old experimentalDecorators
  2. satisfies operator — validate type constraints while keeping precise inferred types
  3. const type parameters — infer literal/tuple types instead of widened types
  4. 30% faster builds — improved incremental compilation and module resolution

1. Standard Decorators

TypeScript 5 implements the TC39 Stage 3 Decorators proposal. The API is different from experimentalDecorators — decorators now receive a context object instead of operating on the prototype directly.

Class Decorator

// Logs every time the class is instantiated
function logged(value: Function, context: ClassDecoratorContext) {
  const className = context.name;

  return class extends value {
    constructor(...args: any[]) {
      super(...args);
      console.log(`[${className}] Instance created`);
    }
  };
}

@logged
class User {
  constructor(public name: string) {}
}

const user = new User('Alice');
// Output: [User] Instance created

Method Decorator — Measure Execution Time

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).toFixed(2)}ms`);
    return result;
  };
}

class DataProcessor {
  @measure
  processLargeDataset(data: number[]): number {
    return data.reduce((sum, n) => sum + n, 0);
  }
}

const processor = new DataProcessor();
processor.processLargeDataset(Array.from({ length: 1_000_000 }, (_, i) => i));
// Output: [processLargeDataset] 5.23ms

Real-World Example: Route Registration

Build a lightweight HTTP router using decorators — similar to how NestJS works:

// Simple route registry
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: 'Alice' }, { id: 2, name: 'Bob' }];
  }

  @Route('/api/posts')
  getPosts() {
    return [{ id: 1, title: 'Hello TypeScript 5' }];
  }
}

// Route dispatch
function handleRequest(path: string) {
  const handler = routes.get(path);
  return handler ? handler() : { error: 'Not Found' };
}

console.log(handleRequest('/api/users'));
// [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }]

Migrating from experimentalDecorators

// TypeScript 4 (experimentalDecorators: true)
function OldDecorator(target: any, key: string, descriptor: PropertyDescriptor) {
  // ...
  return descriptor;
}

// TypeScript 5 (standard, no flag needed)
function NewDecorator(target: Function, context: ClassMethodDecoratorContext) {
  // context.name = method name
  // context.kind = 'method' | 'getter' | 'setter' | 'field' | 'accessor' | 'class'
  // context.static = boolean
  // context.private = boolean
  return target;
}

Update tsconfig.json — remove experimentalDecorators:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "bundler"
  }
}

2. satisfies Operator

The satisfies operator validates that a value conforms to a type while preserving the original inferred type. It solves a common problem with as and type annotations.

The Problem satisfies Solves

type Color = 'red' | 'green' | 'blue' | [number, number, number];

// TypeScript 4 approach — annotate with the type
const palette: Record<string, Color> = {
  primary: 'red',
  secondary: [0, 255, 0],
};

// ❌ TypeScript loses precision — 'red' becomes Color, not 'red'
palette.primary.toUpperCase();    // Error: Property 'toUpperCase' does not exist on type 'Color'
palette.secondary[0];             // Error: 'secondary' might not be an array
// TypeScript 5 — use satisfies
const palette = {
  primary: 'red',
  secondary: [0, 255, 0],
} satisfies Record<string, Color>;

// ✅ Type check passes AND TypeScript keeps the precise types
palette.primary.toUpperCase();    // ✅ primary is still inferred as 'red' (a string)
palette.secondary[0];             // ✅ secondary is still [number, number, number]

Real-World Example: Configuration Object

type Environment = 'development' | 'staging' | 'production';

interface AppConfig {
  env: Environment;
  apiUrl: string;
  features: Record<string, boolean>;
  timeout: number;
}

// satisfies validates the shape while keeping literal types
const config = {
  env: 'production',
  apiUrl: 'https://api.example.com',
  features: {
    darkMode: true,
    analyticsV2: false,
    betaSearch: false,
  },
  timeout: 30_000,
} satisfies AppConfig;

// ✅ TypeScript knows config.env is literally 'production', not just Environment
if (config.env === 'production') {
  enableProductionMonitoring();
}

// ✅ Feature flags are boolean, not unknown
console.log(config.features.darkMode);  // boolean

satisfies vs as vs Type Annotation

Type Annotationassatisfies
Type checking✅ Yes❌ No (overrides)✅ Yes
Preserves inferred type❌ Widens to annotation❌ Widens to cast✅ Yes
Autocomplete precisionLowerLowerHigher
Use whenYou want to enforce the typeYou know better than TSYou want validation + precision

3. const Type Parameters

Without const, generic functions widen literal types to their base types. const tells TypeScript to infer the narrowest possible type.

The Problem

// TypeScript 4
function makeArray<T>(items: T[]) {
  return items;
}

const arr = makeArray([1, 2, 3]);
// Inferred type: number[]   ← widened
// Desired type:  readonly [1, 2, 3]  ← literal tuple

The Solution

// TypeScript 5 — add const to the type parameter
function makeArray<const T>(items: T[]) {
  return items;
}

const arr = makeArray([1, 2, 3]);
// Inferred type: readonly [1, 2, 3]  ✅

Real-World Example: Type-Safe Route Config

function defineRoutes<const T extends Record<string, string>>(routes: T) {
  return routes;
}

const routes = defineRoutes({
  home: '/',
  about: '/about',
  contact: '/contact',
});

// TypeScript knows the exact route names
type RouteName = keyof typeof routes;  // 'home' | 'about' | 'contact'

function navigate(route: RouteName) {
  window.location.href = routes[route];
}

navigate('home');     // ✅
navigate('invalid');  // ❌ Type error at compile time

4. Performance Improvements

TypeScript 5 is 30% faster than TypeScript 4.9 for builds. Key improvements:

  • Faster module resolution with the new bundler module resolution mode
  • Improved incremental build caching
  • Reduced redundant work during type checking
# TypeScript 4.9 (large project)
tsc --build   # ~120 seconds

# TypeScript 5
tsc --build   # ~84 seconds

New bundler Module Resolution

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "bundler",    // New in TS 5 — for Vite, webpack, esbuild
    "allowImportingTsExtensions": true, // Import .ts files directly
    "noEmit": true                    // Let your bundler handle output
  }
}

5. Other New Features

All Enums are Union Enums

enum Status {
  Pending = 'pending',
  Active = 'active',
  Inactive = 'inactive',
}

// TypeScript 5: enums behave like union types
type StatusLiteral = `${Status}`;  // 'pending' | 'active' | 'inactive'

function processStatus(status: `${Status}`) {
  // Works with both the enum member and string literals
}
processStatus(Status.Pending);  // ✅
processStatus('pending');       // ✅

export type *

// types.ts
export type User = { id: number; name: string };
export type Post = { id: number; title: string };
export type Comment = { id: number; body: string };

// index.ts — re-export only types (no values)
export type * from './types';

Improved switch(true) Narrowing

function describe(value: string | number | boolean): string {
  switch (true) {
    case typeof value === 'string':
      return value.toUpperCase();  // TypeScript 5: value is narrowed to string ✅

    case typeof value === 'number':
      return value.toFixed(2);     // narrowed to number ✅

    default:
      return String(value);
  }
}

Migration Guide: TypeScript 4 → 5

# 1. Update TypeScript
npm install -D typescript@latest

# 2. Check for errors
npx tsc --noEmit

# 3. Fix any new errors, then build
npm run build

Key tsconfig.json changes:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "experimentalDecorators": false,
    "strict": true,
    "skipLibCheck": true,
    "esModuleInterop": true
  }
}

What to watch for:

  • If you use experimentalDecorators, keep it set to true until your decorator libraries (NestJS, TypeORM) publish TS5-compatible updates
  • The new bundler resolution mode may catch previously-ignored import issues
  • export type * is a new syntax — older TS versions won’t accept it

Summary

FeatureWhat it solvesWhen to use
Standard DecoratorsReplaces experimentalDecoratorsClass-level metadata, AOP patterns
satisfiesValidation without losing precisionConfig objects, option maps
const type paramsLiteral/tuple inference in genericsRoute config, event registries
bundler resolutionWorks with Vite/webpack importsModern frontend projects

Related posts:


Frequently Asked Questions (FAQ)

Q. When would I use this in practice?

A. Complete guide to TypeScript 5 Complete Guide | Decorators, satisfies, const Type Parameters.

Q. What should I read before this?

A. Follow the previous article or related articles links at the bottom of each post to learn in sequence.

Q. Where can I study this more deeply?

A. Check cppreference and the relevant library’s official documentation. The reference links at the end of the article are also worth using.


Other articles related to this topic.


Keywords Covered in This Article (Related Search Terms)

This article covers TypeScript, TypeScript 5, JavaScript, Frontend, Type Safety, Decorators.