TypeScript 5.0의 새로운 기능과 마이그레이션 가이드
TypeScript 5.0의 새로운 기능과 마이그레이션 가이드
TypeScript는 JavaScript에 정적 타입을 추가한 슈퍼셋 언어로, 대규모 애플리케이션 개발의 사실상 표준이 되었습니다. TypeScript 5.0은 성능 개선과 함께 개발자 경험을 크게 향상시키는 새로운 기능들을 도입했습니다.
TypeScript 5.0의 주요 기능
1. Decorators (Stage 3)
ECMAScript Stage 3 데코레이터를 공식 지원합니다. 이전의 실험적 데코레이터와 달리 표준을 따릅니다.
function logged(target: any, context: ClassMethodDecoratorContext) {
const methodName = String(context.name);
return function(this: any, ...args: any[]) {
console.log(`Calling ${methodName} with`, args);
const result = target.call(this, ...args);
console.log(`${methodName} returned`, result);
return result;
};
}
class Calculator {
@logged
add(a: number, b: number) {
return a + b;
}
}
const calc = new Calculator();
calc.add(2, 3);
// Calling add with [2, 3]
// add returned 5
2. const Type Parameters
제네릭 타입 파라미터를 const로 선언하여 더 정확한 타입 추론이 가능합니다.
// 이전 방식
function oldWay<T>(arr: T[]) {
return arr;
}
const result1 = oldWay([1, 2, 3]); // number[]
// 5.0의 const 방식
function newWay<const T>(arr: T[]) {
return arr;
}
const result2 = newWay([1, 2, 3]); // readonly [1, 2, 3]
이를 통해 배열과 객체의 리터럴 타입이 보존됩니다.
3. Supporting Multiple Configuration Files in extends
tsconfig.json에서 여러 설정 파일을 확장할 수 있습니다.
{
"extends": ["./base.json", "./strict.json", "./paths.json"],
"compilerOptions": {
"outDir": "./dist"
}
}
4. All enums Are Union enums
모든 enum이 자동으로 유니온 타입처럼 동작하여 타입 체크가 더 엄격해집니다.
enum Color {
Red,
Green,
Blue
}
function getColorName(color: Color) {
switch (color) {
case Color.Red:
return "Red";
case Color.Green:
return "Green";
// Blue 케이스 누락 시 컴파일 에러!
}
}
5. Speed Improvements
TS 5.0은 TS 4.9 대비 최대 3배 빠른 타입 체크 속도를 보여줍니다. 특히 대규모 프로젝트에서 체감 속도가 크게 향상되었습니다.
고급 타입 패턴
Conditional Types
type IsString<T> = T extends string ? true : false;
type A = IsString<"hello">; // true
type B = IsString<42>; // false
// 실전 예제: API 응답 타입
type ApiResponse<T> = T extends { error: any }
? { success: false; error: T["error"] }
: { success: true; data: T };
type UserResponse = ApiResponse<{ id: number; name: string }>;
// { success: true; data: { id: number; name: string } }
type ErrorResponse = ApiResponse<{ error: string }>;
// { success: false; error: string }
Mapped Types
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
type Partial<T> = {
[P in keyof T]?: T[P];
};
// 실전 예제: API 업데이트 타입
interface User {
id: number;
name: string;
email: string;
age: number;
}
type UserUpdate = Partial<Omit<User, 'id'>>; // id는 변경 불가
// { name?: string; email?: string; age?: number; }
Template Literal Types
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
type Endpoint = '/users' | '/posts' | '/comments';
type ApiRoute = `${HttpMethod} ${Endpoint}`;
// "GET /users" | "GET /posts" | ... | "DELETE /comments"
// 실전 예제: 이벤트 타입
type EventName = 'click' | 'focus' | 'change';
type EventHandler<T extends EventName> = `on${Capitalize<T>}`;
type ClickHandler = EventHandler<'click'>; // "onClick"
엄격한 타입 설정
프로덕션 프로젝트를 위한 권장 tsconfig.json:
{
"compilerOptions": {
// 엄격한 타입 체크
"strict": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true,
"exactOptionalPropertyTypes": true,
// 모듈 설정
"module": "ESNext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
// 출력 설정
"target": "ES2022",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"outDir": "./dist",
"declaration": true,
"declarationMap": true,
"sourceMap": true,
// Interop
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
// 스킵 설정
"skipLibCheck": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
Type Guards와 Narrowing
// Type Predicate
function isString(value: unknown): value is string {
return typeof value === 'string';
}
function processValue(value: string | number) {
if (isString(value)) {
console.log(value.toUpperCase()); // string으로 좁혀짐
} else {
console.log(value.toFixed(2)); // number로 좁혀짐
}
}
// Discriminated Union
interface Success {
type: 'success';
data: any;
}
interface Error {
type: 'error';
message: string;
}
type Result = Success | Error;
function handleResult(result: Result) {
if (result.type === 'success') {
console.log(result.data); // Success 타입으로 좁혀짐
} else {
console.log(result.message); // Error 타입으로 좁혀짐
}
}
Utility Types 활용
interface User {
id: number;
name: string;
email: string;
password: string;
createdAt: Date;
}
// 필요한 필드만 선택
type PublicUser = Pick<User, 'id' | 'name' | 'email'>;
// 특정 필드 제외
type UserWithoutPassword = Omit<User, 'password'>;
// 모든 필드를 선택적으로
type PartialUser = Partial<User>;
// 모든 필드를 필수로
type RequiredUser = Required<Partial<User>>;
// 특정 타입의 값들로 이루어진 객체
type UserRecord = Record<string, User>;
// 함수 반환 타입 추출
function getUser() {
return { id: 1, name: 'John' };
}
type UserReturnType = ReturnType<typeof getUser>;
// { id: number; name: string; }
React와 TypeScript
import { useState, useEffect } from 'react';
interface User {
id: number;
name: string;
email: string;
}
interface Props {
userId: number;
onUserLoad?: (user: User) => void;
}
function UserProfile({ userId, onUserLoad }: Props) {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
fetch(`/api/users/${userId}`)
.then(res => res.json() as Promise<User>)
.then(data => {
setUser(data);
onUserLoad?.(data);
})
.catch(setError)
.finally(() => setLoading(false));
}, [userId, onUserLoad]);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
if (!user) return null;
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
);
}
마이그레이션 전략
1. 점진적 마이그레이션
JavaScript 프로젝트를 한 번에 TypeScript로 바꾸지 말고, 파일 하나씩 천천히 변환하세요.
// tsconfig.json
{
"compilerOptions": {
"allowJs": true, // JS 파일 허용
"checkJs": false, // JS 파일은 타입 체크 안 함
"strict": false // 처음에는 느슨하게
}
}
2. @ts-check 활용
JS 파일에서도 타입 체크를 적용할 수 있습니다.
// @ts-check
/**
* @param {number} a
* @param {number} b
* @returns {number}
*/
function add(a, b) {
return a + b;
}
3. 단계별 strict 모드 활성화
{
"compilerOptions": {
"noImplicitAny": true, // 1단계
"strictNullChecks": true, // 2단계
"strictFunctionTypes": true, // 3단계
"strictBindCallApply": true, // 4단계
"strictPropertyInitialization": true, // 5단계
"noImplicitThis": true, // 6단계
"alwaysStrict": true, // 7단계
// 또는 위 전체를 한 번에
"strict": true
}
}
결론
TypeScript 5.0은 성능과 개발자 경험을 크게 향상시킨 메이저 업데이트입니다. 데코레이터, const 타입 파라미터, 다중 설정 파일 확장 등 실용적인 기능들이 추가되었으며, 타입 체크 속도도 대폭 개선되었습니다.
GMI는 TypeScript 기반의 타입 안전한 웹 애플리케이션을 개발합니다. JavaScript 프로젝트의 TypeScript 마이그레이션부터 고급 타입 시스템 설계까지, 전문적인 컨설팅과 개발 서비스를 제공합니다.