숏컷 코드
interface User {
id: string;
name: string;
email: string;
}
type UserPatch = Partial<User>;
type UserSummary = Pick<User, "id" | "name">;유틸리티 타입
유틸리티 타입 입문에서 먼저 자주 보는 도구는 아래 여섯 가지입니다.
Partial<T>Required<T>Readonly<T>Pick<T, K>Omit<T, K>Record<K, V>
1) 속성 제약만 바꾸면 Partial, Required, Readonly
원본 구조는 유지하되 optional/required/readonly 성격만 바꾸고 싶다면 이 계열이 맞습니다.
type UserPatch = Partial<User>;
type LockedUser = Readonly<User>;
type FullUser = Required<User>;업데이트 DTO, 설정 오버라이드, 불변 상태 표현처럼 쓰임새가 분명합니다.
2) 일부만 고르거나 빼면 Pick, Omit
원본 타입 중 특정 필드만 노출하거나, 몇 개를 제외한 버전을 만들고 싶다면 Pick과 Omit이 가장 직관적입니다.
type UserListItem = Pick<User, "id" | "name">;
type PublicUser = Omit<User, "email">;즉 "도메인 모델 하나에서 여러 뷰 타입을 뽑는다"는 감각으로 읽으면 됩니다.
3) 키 집합으로 새 매핑을 만들면 Record
값 집합이 아니라 키 집합에서 새 객체 매핑을 만들고 싶다면 Record<K, V>가 맞습니다.
type StatusMap = Record<"idle" | "done", string>;상태별 라벨, 권한별 설명, enum-like literal union의 매핑에서 자주 씁니다.
type Status = "idle" | "loading" | "done";
const labels: Record<Status, string> = {
idle: "대기",
loading: "로딩",
done: "완료",
};반대로 "객체 타입에서 몇 필드만 뽑는다"는 문제에는 Record보다 Pick이나 Omit이 더 직접적입니다.
4) 유틸리티 타입은 도메인 모델의 복붙을 줄이는 장치다
원본 타입이 바뀌었을 때 파생 타입도 같이 따라가게 만드는 것이 큰 장점입니다. 같은 구조를 조금씩 복붙하는 대신, 변형 의도만 남길 수 있습니다.
5) 목적 이름 없이 남발하면 오히려 더 모호해진다
유틸리티 타입 자체는 간단하지만, 의미 없는 별칭 이름과 섞이면 독자가 왜 이 변형이 필요한지 이해하기 어려워집니다.
그래서 UserPatch, UserListItem처럼 목적이 드러나는 이름이 중요합니다.
언제 어떤 조합을 쓸까
유틸리티 타입을 고를 때 볼 점
- 부분 업데이트 타입이다:
Partial<T> - 모든 속성을 필수로 만든다:
Required<T> - 읽기 전용으로 만든다:
Readonly<T> - 일부 속성만 뽑는다:
Pick<T, K> - 일부 속성만 뺀다:
Omit<T, K> - 키 집합에서 값 매핑을 만든다:
Record<K, V>
주의할 점
유틸리티 타입이 편하다고 원본 도메인 모델과 다른 의미를 무분별하게 섞어 쓰면 타입 이름이 오히려 더 모호해질 수 있습니다. 변형 타입은 목적이 드러나는 이름과 함께 쓰는 편이 좋습니다.
interface User {
id: string;
name: string;
email: string;
}
// ✅ 목적이 드러나는 별칭
type UserPatch = Partial<User>;
type UserListItem = Pick<User, "id" | "name">;
// ❌ 원본 타입을 복붙해 파생 타입을 손으로 유지
type UserPatchManual = {
id?: string;
name?: string;
email?: string;
};참고 링크
1 sources