Utility Types — Partial, Required, Pick, Omit, Record
유틸리티 타입은 기존 타입을 변환 해서 새로운 타입을 만드는 TypeScript 내장 도구입니다.
Partial<T> — 모든 속성을 선택적으로
interface User {
name: string;
age: number;
email: string;
}
// 모든 속성이 optional로 변경
type PartialUser = Partial<User>;
// { name?: string; age?: number; email?: string; }
// 활용: 업데이트 함수에서 일부 필드만 받을 때
function updateUser(id: number, updates: Partial<User>): User {
const existing: User = { name: '홍길동', age: 25, email: 'test@test.com' };
return { ...existing, ...updates };
}
updateUser(1, { age: 26 }); // name, email은 그대로
updateUser(1, { name: '김철수' }); // age, email은 그대로
Partial의 내부 구현
// 실제 구현 — Mapped Type
type MyPartial<T> = {
[K in keyof T]?: T[K];
};
Required<T> — 모든 속성을 필수로
interface Config {
host?: string;
port?: number;
ssl?: boolean;
}
// 모든 속성이 필수로 변경
type StrictConfig = Required<Config>;
// { host: string; port: number; ssl: boolean; }
// 활용: 기본값이 모두 채워진 최종 설정
function createConfig(overrides: Config): Required<Config> {
return {
host: overrides.host ?? 'localhost',
port: overrides.port ?? 3000,
ssl: overrides.ssl ?? false,
};
}
Required의 내부 구현
type MyRequired<T> = {
[K in keyof T]-?: T[K]; // -?로 optional을 제거
};
Pick<T, K> — 특정 속성만 선택
interface User {
id: number;
name: string;
age: number;
email: string;
password: string;
}
// 필요한 속성만 골라 새 타입 생성
type UserProfile = Pick<User, 'id' | 'name' | 'email'>;
// { id: number; name: string; email: string; }
// 활용: API 응답에서 민감한 정보 제외
function getPublicProfile(user: User): Pick<User, 'id' | 'name'> {
return { id: user.id, name: user.name };
}
Pick의 내부 구현
type MyPick<T, K extends keyof T> = {
[P in K]: T[P];
};
Omit<T, K> — 특정 속성을 제외
interface User {
id: number;
name: string;
age: number;
email: string;
password: string;
}
// password를 제외한 타입
type SafeUser = Omit<User, 'password'>;
// { id: number; name: string; age: number; email: string; }
// 여러 속성 제외
type PublicUser = Omit<User, 'password' | 'email'>;
// { id: number; name: string; age: number; }
// 활용: 생성 시 id는 자동 생성되므로 제외
type CreateUserInput = Omit<User, 'id'>;
Pick vs Omit — 언제 무엇을 쓸까
// 속성이 많고 일부만 필요할 때 → Pick
type Summary = Pick<User, 'name' | 'email'>;
// 속성이 많고 일부만 빼야 할 때 → Omit
type WithoutPassword = Omit<User, 'password'>;
Record<K, V> — 키-값 매핑
// 특정 키들에 같은 타입의 값을 매핑
type Fruit = 'apple' | 'banana' | 'cherry';
type FruitInfo = Record<Fruit, { color: string; price: number }>;
const fruits: FruitInfo = {
apple: { color: '빨강', price: 3000 },
banana: { color: '노랑', price: 2000 },
cherry: { color: '빨강', price: 5000 },
// 세 과일이 모두 있어야 함
};
// 동적 키
type StringMap = Record<string, number>;
const scores: StringMap = {
math: 90,
english: 85,
};
Record의 내부 구현
type MyRecord<K extends keyof any, V> = {
[P in K]: V;
};
유틸리티 타입 조합
실무에서는 여러 유틸리티 타입을 조합해서 사용합니다.
interface User {
id: number;
name: string;
email: string;
createdAt: Date;
updatedAt: Date;
}
// 생성 입력: id와 날짜 필드 제외
type CreateUserInput = Omit<User, 'id' | 'createdAt' | 'updatedAt'>;
// 수정 입력: id 제외 + 나머지 선택적
type UpdateUserInput = Partial<Omit<User, 'id' | 'createdAt' | 'updatedAt'>>;
// 목록 표시: 핵심 정보만
type UserListItem = Pick<User, 'id' | 'name'>;
// 응답 형식
type ApiResponse<T> = {
data: T;
status: number;
};
type UserResponse = ApiResponse<Omit<User, 'updatedAt'>>;
Readonly<T>
interface Config {
host: string;
port: number;
}
const config: Readonly<Config> = {
host: 'localhost',
port: 3000,
};
// config.host = 'remote'; // ❌ Error — 읽기 전용
면접에서 자주 묻는 질문
면접에서 "Partial의 내부 구현을 설명해 보세요"라는 질문이 나옵니다.
// 핵심은 Mapped Types
type Partial<T> = {
[K in keyof T]?: T[K];
};
// keyof T — T의 모든 키를 유니온으로
// K in — 각 키를 순회
// ? — 선택적으로 변환
// T[K] — 해당 키의 값 타입
정리
| 유틸리티 | 역할 | 활용 |
|---|---|---|
Partial<T> | 모든 속성을 선택적으로 | 업데이트 입력 |
Required<T> | 모든 속성을 필수로 | 기본값 채운 최종 설정 |
Pick<T, K> | 특정 속성만 선택 | API 응답 축소 |
Omit<T, K> | 특정 속성 제외 | 민감 정보 제거 |
Record<K, V> | 키-값 매핑 | 딕셔너리, 매핑 |
Readonly<T> | 모든 속성을 읽기 전용 | 불변 설정 |
- 유틸리티 타입의 내부 구현은 Mapped Types로 이해할 수 있다
- 실무에서는 여러 유틸리티를 조합해서 사용하는 것이 일반적이다
댓글 로딩 중...