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가 필요합니다.
- emitDecoratorMetadata는 reflect-metadata 라이브러리와 함께 사용할 때 유용합니다. ( NestJS 등에서 사용합니다. )
- 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의 엄격한 모듈 해석을 따라야 합니다.
- 사용자들이 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 |