유틸리티 타입 (Partial, Readonly, Pick 등)
타입스크립트는 일반적인 타입 변환을 쉽게 수행할 수 있도록 여러 유틸리티 타입을 제공합니다.
이들은 기존 타입을 변형하여 새로운 타입을 생성하는 데 사용됩니다.
주요 유틸리티 타입 개요
Partial<T>
: 모든 속성을 선택적으로 만듭니다.Readonly<T>
: 모든 속성을 읽기 전용으로 만듭니다.Pick<T, K>
: 특정 속성만 선택합니다.Omit<T, K>
: 특정 속성을 제외합니다.Record<K, T>
: 키 타입 K와 값 타입 T를 가진 객체 타입을 생성합니다.Exclude<T, U>
: T에서 U에 할당할 수 있는 타입을 제외합니다.Extract<T, U>
: T에서 U에 할당할 수 있는 타입을 추출합니다.NonNullable<T>
: null과 undefined를 제외합니다.ReturnType<T>
: 함수의 반환 타입을 얻습니다.Parameters<T>
: 함수의 매개변수 타입을 튜플로 얻습니다.
Partial<T>
Partial<T>
는 모든 속성을 선택적으로 만듭니다.
interface User {
id: number;
name: string;
email: string;
}
function updateUser(user: User, fieldsToUpdate: Partial<User>) {
return { ...user, ...fieldsToUpdate };
}
const user: User = {
id: 1,
name: "John Doe",
email: "[email protected]"
};
const updatedUser = updateUser(user, { email: "[email protected]" });
내부 구현
type Partial<T> = {
[P in keyof T]?: T[P];
};
Readonly<T>
Readonly<T>
는 모든 속성을 읽기 전용으로 만듭니다.
interface Config {
host: string;
port: number;
}
const config: Readonly<Config> = {
host: "localhost",
port: 3000
};
// config.port = 8080; // Error: Cannot assign to 'port' because it is a read-only property.
불변성을 강제하여 예기치 않은 변경을 방지할 수 있습니다.
Pick<T, K>와 Omit<T, K>
Pick<T, K>
는 특정 속성만 선택하고, Omit<T, K>
는 특정 속성을 제외합니다.
interface User {
id: number;
name: string;
email: string;
password: string;
}
type PublicUser = Omit<User, "password">;
type LoginCredentials = Pick<User, "email" | "password">;
function displayUser(user: PublicUser) {
console.log(`${user.name} (${user.email})`);
}
function login(credentials: LoginCredentials) {
// 로그인 로직
}
Record<K, T>
Record<K, T>
는 키 타입 K와 값 타입 T를 가진 객체 타입을 생성합니다.
type CityDatabase = Record<string, number>;
const populations: CityDatabase = {
"New York": 8419000,
"Los Angeles": 3898000,
"Chicago": 2746000
};
function getPopulation(city: string): number {
return populations[city] || 0;
}
유니온 타입 조작 3종
Exclude<T, U>
Extract<T, U>
NonNullable<T>
이들은 유니온 타입을 조작하는 데 유용합니다.
type NumberOrString = number | string | null | undefined;
type JustNumberOrString = NonNullable<NumberOrString>; // number | string
type JustString = Extract<NumberOrString, string>; // string
type NoString = Exclude<NumberOrString, string>; // number | null | undefined
function processValue(value: JustNumberOrString) {
if (typeof value === "string") {
return value.toUpperCase();
} else {
return value * 2;
}
}
ReturnType<T>와 Parameters<T>
이들은 함수 타입을 조작하는 데 사용됩니다.
function greet(name: string, age: number): string {
return `Hello, ${name}! You are ${age} years old.`;
}
type GreetReturn = ReturnType<typeof greet>; // string
type GreetParams = Parameters<typeof greet>; // [string, number]
function logged<T extends (...args: any[]) => any>(fn: T) {
return (...args: Parameters<T>): ReturnType<T> => {
console.log(`Calling function with args:`, args);
const result = fn(...args);
console.log(`Function returned:`, result);
return result;
};
}
const loggedGreet = logged(greet);
loggedGreet("Alice", 30);
유틸리티 타입 조합
여러 유틸리티 타입을 조합하여 복잡한 타입 변환을 수행할 수 있습니다.
interface User {
id: number;
name: string;
email: string;
role: "admin" | "user";
metadata: Record<string, string>;
}
type UpdateableUserFields = Omit<User, "id">;
type OptionalUpdateableUserFields = Partial<UpdateableUserFields>;
function updateUser(userId: number, fieldsToUpdate: OptionalUpdateableUserFields) {
// 업데이트 로직
}
사용자 정의 유틸리티 타입
프로젝트 특화 유틸리티 타입을 정의할 수 있습니다.
type Mutable<T> = {
-readonly [P in keyof T]: T[P];
};
type DeepPartial<T> = T extends object ? {
[P in keyof T]?: DeepPartial<T[P]>;
} : T;
interface DeepNestedConfig {
readonly server: {
readonly host: string;
readonly port: number;
};
readonly database: {
readonly url: string;
readonly name: string;
};
}
type MutableDeepPartialConfig = DeepPartial<Mutable<DeepNestedConfig>>;
function updateConfig(config: DeepNestedConfig, updates: MutableDeepPartialConfig) {
// 깊은 업데이트 로직
}
주의사항과 최적화
- 성능 : 복잡한 유틸리티 타입은 컴파일 시간을 증가시킬 수 있습니다.
- 가독성 : 과도하게 중첩된 유틸리티 타입은 코드 이해를 어렵게 만들 수 있습니다.
최적화 전략
- 자주 사용되는 복잡한 타입은 별도의 타입 별칭으로 정의하세요.
- 타입 추론을 최대한 활용하여 명시적 타입 주석을 줄이세요.
- 필요한 경우에만 고급 유틸리티 타입을 사용하세요.
설계 원칙과 Best Practices
- 단순성 유지 : 가능한 한 간단한 유틸리티 타입 조합을 사용하세요.
- 재사용성 고려 : 프로젝트 전반에서 사용될 수 있는 유틸리티 타입은 별도 파일로 분리하세요.
- 명확한 이름 사용 : 유틸리티 타입의 목적을 명확히 표현하는 이름을 사용하세요.
- 문서화 : 복잡한 유틸리티 타입은 주석으로 설명하세요.
- 테스트 작성 : 중요한 유틸리티 타입에 대한 타입 테스트를 작성하세요.
- 일관성 유지 : 프로젝트 전체에서 유틸리티 타입 사용 패턴을 일관되게 유지하세요.
- IDE 지원 활용 : 유틸리티 타입이 IDE의 자동 완성과 타입 추론을 방해하지 않는지 확인하세요.
- 버전 관리 : 타입스크립트 버전 업그레이드 시 유틸리티 타입의 변경사항을 확인하세요.
- 실제 사용 사례 기반 : 실제 문제 해결을 위해 유틸리티 타입을 사용하고, 과도한 추상화는 피하세요.
- 학습과 공유 : 팀 내에서 유용한 유틸리티 타입 패턴을 공유하고 학습하세요.