Typescript 5
2025. 5. 19. 13:27
728x90
반응형

해당 글을 최신 typescript 5 버전을 보다 더 잘 사용하기 위해 작성하였습니다.

typescript 5.0의 주요 변경 내용들을 정리하였습니다.

 

Decorators 정식 지원

  • typescript 5 버전부터는 ECMAScript 표준 기반의 장식자(Decorators)를 정식으로 지원합니다.
  • tsconfig.json
{
  "compilerOptions": {
    "target": "ES2022",            // 최소 ES2022 이상
    "experimentalDecorators": true, // 데코레이터 문법 활성화
    "emitDecoratorMetadata": true,  // (선택 사항) 리플렉션 메타데이터 사용 시 필요
    "useDefineForClassFields": true // 클래스 필드 정의 방식 일치
  }
}
  • emitDecoratorMetadata
    • emitDecoratorMetadata는 reflect-metadata 라이브러리와 함께 사용할 때 유용합니다. ( NestJS 등에서 사용합니다. ) 
      • reflect-metadata는 메타데이터(metadata) 를 클래스, 메서드, 매개변수 등에 저장하고 꺼낼 수 있게 해주는 런타임 리플렉션 API입니다.
      • TypeScript의 emitDecoratorMetadata 옵션이 활성화되면 컴파일 시 자동으로 클래스나 메서드에 타입 정보를 메타데이터로 삽입합니다. 그러나 이 정보는 런타임에 접근하기 위한 수단이 없기 때문에, reflect-metadata가 필요합니다.
  • useDefineForClassFields
    • useDefineForClassFields는 클래스 필드 정의 시 ECMAScript 표준 방식으로 정의합니다. 이는 TypeScript 5에서 ECMAScript 데코레이터를 사용할 때 정해진 형태로 인식되도록 하기 위해 사용됩니다.
    • false일 경우, 필드가 Object.defineProperty으로 정의되지 않기 때문에 데코레이터에서 의도한 설정이 덮어져서 올바르게 작동되지 않을 가능성이 높습니다.

 

Decorators 종류

  • 데코레이터는 인스턴스를 만들지 않아도 클래스가 정의될 때 한 번 실행됩니다.
  • 실행 순서는 필드 → 접근자 → 메서드 → 클래스 순서로 실행됩니다.
  • 필드 데코레이터
function Uppercase(target: any, propertyKey: string) {
  let value = target[propertyKey];

  const getter = () => value;
  const setter = (newVal: string) => {
    value = newVal.toUpperCase();
  };

  Object.defineProperty(target, propertyKey, {
    get: getter,
    set: setter,
    enumerable: true,
    configurable: true,
  });
}

class User {
  @Uppercase
  name: string = '';
}

const user = new User();
user.name = 'john';
console.log(user.name); // 'JOHN'

 

  • 접근자 데코레이터
function LogAccess(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  const originalGet = descriptor.get;
  descriptor.get = function () {
    console.log(`Accessing ${propertyKey}`);
    return originalGet?.call(this);
  };
}

class Product {
  private _price = 100;

  @LogAccess
  get price() {
    return this._price;
  }
}

 

  • 메서드 데코레이터
function Log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  const originalMethod = descriptor.value;
  descriptor.value = function (...args: any[]) {
    console.log(`Calling ${propertyKey} with`, args);
    return originalMethod.apply(this, args);
  };
}

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

 

  • 클래스 데코레이터
function Logger<T extends { new(...args: any[]): {} }>(constructor: T) {
  return class extends constructor {
    createdAt = new Date();
  };
}

@Logger // Logger는 클래스를 래핑하는 함수입니다. 인스턴스 생성 시 새로운 속성 createdAt을 추가합니다.
class MyService {
  constructor(public name: string) {}
}

 

 

const 타입 파라미터

  • typescript 5 버전부터는 함수의 제네릭에서 const 제약조건을 명시할 수 있습니다.
  • 객체나 배열 리터럴을 제네릭으로 넘길 때 as const를 명시하지 않아도 literal 타입 추론이 가능해지게 되었습니다.
type HasNames = { names: readonly string[] };

function getNamesExactly<T extends HasNames>(arg: T): T["names"] {
    return arg.names;
}

const names = getNamesExactly({ names: ["Alice", "Bob", "Eve"] });
// 타입: readonly ["Alice", "Bob", "Eve"]

// 이전까지는 string[]으로 추론하였고, 보다 정확하게 추론하기 위해서는 as const 키워드가 필요했습니다.
// typescript 5 부터는 as const 키워드 없이도 보다 정확한 타입 추론이 가능해졌습니다.

 

모든 Enum은 Union Enum이다.

  • TypeScript 5에서는 계산된 enum 멤버조차도 고유 타입을 가지게 개선되었습니다.
// 이전
enum E {
  Blah = Math.random() // 값 추론 불가 → 리터럴 타입 못 씀
}

function isBlah(x: E): boolean {
  return x === E.Blah; // ❌ x는 number로 추론됨, 비교 효과 없음
}

// typescript 5
enum E {
  Blah = Math.random() // 리터럴 타입처럼 취급
}

function isBlah(x: E): x is E.Blah {
  return x === E.Blah; // ✅ x는 E.Blah로 좁혀짐
}

 

--moduleResolution : bundler

  • TypeScript 5에서는 현대적인 번들러의 동작 방식에 맞춘 새로운 해석 전략인 bundler 모드를 도입했습니다.
  • 특징
    • import './utils'와 같이 .js, .ts 등의 확장자 생략이 가능합니다.
    • Vite, esbuild, swc, Parcel, Webpack 등에 적합합니다. 
  • 언제 사용하는 것이 좋을까?
    • Vite, esbuild, Webpack, swc 등 모던 번들러를 사용하는 경우
    • 파일 확장자를 생략하고 싶을 때
    • 엄격한 Node.js 룰을 강제하고 싶지 않은 경우
  • npm에 배포하는 라이브러리를 개발 중인 경우, nodenext를 쓰는 것이 더 적합합니다.
    • 사용자들이 Node.js 환경에서 직접 ESM/CJS로 사용할 수 있기 때문에 Node의 엄격한 모듈 해석을 따라야 합니다.

 

--verbatimModuleSyntax

  • import 구문을 수정하지 않고 그대로 유지하도록 설정해주는 옵션으로 기존의 importsNotUsedAsValues와  preserveImportsNotUsedAsValues를 대체합니다.
  • TypeScript 모듈 처리의 예측 가능성과 안정성을 크게 높여줍니다.

 

export type * 지원

  • 이전 버전에서는 재수출(re-export) 문법에는 아래 두 가지 형태에서 type 전용 사용이 허용되지 않았습니다.
  • typescript 5 부터는 export type * 공식 지원합니다.
  • 장점
    • 타입과 값의 명확한 분리가 가능하고 불필요한 런타임 코드 생성을 방지하고 모듈 구조 유지에 용이합니다.
    • 번들러가 불필요한 코드 제거 가능하고 순수 타입 모듈로 분리가 가능하여 모듈 트리셰이킹(Tree-shaking) 최적화에 용이합니다.
    • 타입만 외부에 공개하고, 내부 구현은 감출 수 있어 라이브러리 작성 시 유용합니다.
// types.ts
export type A = { name: string };
export interface B { id: number }

// index.ts
export type * from "./types"; // 모듈 내의 모든 타입 항목만 재수출합니다.

// models/vehicles.ts
export class Spaceship {
  // 클래스 정의
}

// models/index.ts
export type * as vehicles from "./vehicles"; // 모듈 전체를 네임스페이스로 묶은 형태로 타입만 재수출합니다.

// main.ts
import { vehicles } from "./models";

// ✅ 타입으로는 사용 가능
function takeASpaceship(s: vehicles.Spaceship) {}

 

JSDoc ( @satisfies, @overload )

  • @satisfies
    • 많은 개발자들이 .js 파일에서 JSDoc으로 타입을 지정하며 TypeScript를 활용하고 있습니다. 그들을 위해 @satisfies가 JSDoc에서도 사용 가능하게 되었습니다.
  • @overload
    • JavaScript에서 JSDoc을 사용해 함수 오버로드(overload) 를 선언할 수 있도록 @overload 태그를 지원합니다.
// @satisfies 형식
/**
 * @satisfies {타입}
 */
const 변수명 = {
  // 속성들
};


// @overload 형식
/**
 * @overload
 * @param {타입1} param1
 * @param {타입2} param2
 * @returns {리턴타입}
 */
/**
 * @overload
 * @param {...} ...
 * @returns {...}
 */
/**
 * @param {최종 통합 타입}
 */
function 실제함수(...) {
  // 구현
}
728x90
반응형

'typescript' 카테고리의 다른 글

Typescript ( CFA )  (1) 2025.05.15
Typescript ( Structural Typing )  (0) 2025.05.13
Typescript ( type vs interface )  (0) 2025.05.11