유틸리티 타입(Utility Types) 활용기: Partial, Pick, Omit, Readonly 등 실무 필수 타입

타입스크립트를 사용하다 보면 이미 정의된 인터페이스나 타입을 조금만 수정해서 재사용하고 싶은 경우가 많습니다. 이때 유틸리티 타입을 활용하면 코드 중복을 획기적으로 줄이고 타입 안정성을 유지할 수 있습니다.
1. Partial<T>: 모든 프로퍼티를 선택 사항으로
Partial 타입은 인터페이스의 모든 프로퍼티를 optional(?)로 변환합니다. 주로 수정(Update) 기능을 구현할 때 유용합니다.
실무 예제: 프로필 정보 업데이트
사용자의 전체 정보 중 변경된 부분만 서버에 보낼 때 사용합니다.
interface User {
id: number;
name: string;
email: string;
age: number;
}
// Partial<User>를 사용하면 모든 속성이 있어도 되고 없어도 되는 상태가 됩니다.
function updateUser(id: number, fieldsToUpdate: Partial<User>) {
console.log(`${id}번 유저의 정보를 업데이트합니다:`, fieldsToUpdate);
}
// 모든 필드를 채울 필요 없이 원하는 필드만 전달 가능
updateUser(1, { name: "John Doe" });
updateUser(2, { email: "new@example.com", age: 30 });
2. Pick<T, K>: 원하는 프로퍼티만 쏙쏙
Pick은 인터페이스에서 특정 키(K)들만 선택하여 새로운 타입을 만듭니다. 요약 정보나 목록을 보여줄 때 자주 사용됩니다.
실무 예제: 유저 목록 미리보기
유저의 전체 정보 중 이름과 이메일만 필요한 요약 리스트를 만들 때 유용합니다.
interface Product {
id: string;
name: string;
price: number;
description: string;
stock: number;
}
// Product에서 name과 price만 선택합니다.
type ProductPreview = Pick<Product, "name" | "price">;
const items: ProductPreview[] = [
{ name: "기계식 키보드", price: 150000 },
{ name: "무선 마우스", price: 80000 }
];
3. Omit<T, K>: 특정 프로퍼티만 제외하기
Omit은 Pick의 반대입니다. 특정 키(K)를 제외한 나머지 모든 프로퍼티를 포함하는 타입을 만듭니다. 민감한 정보(비밀번호 등)를 제거할 때 필수적입니다.
실무 예제: 보안을 위한 데이터 필터링
유저 정보를 클라이언트에 보낼 때 비밀번호나 내부 관리용 데이터는 숨겨야 합니다.
interface UserAccount {
uuid: string;
username: string;
passwordHash: string;
lastLogin: Date;
isAdmin: boolean;
}
// 비밀번호 해시와 관리자 여부만 뺀 타입을 만듭니다.
type PublicUserProfile = Omit<UserAccount, "passwordHash" | "isAdmin">;
function getProfile(user: UserAccount): PublicUserProfile {
const { passwordHash, isAdmin, ...publicInfo } = user;
return publicInfo;
}
4. Readonly<T>: 수정을 허용하지 않는 타입
Readonly는 모든 프로퍼티를 readonly로 만듭니다. **설정값(Config)**이나 **상태(State)**가 외부에서 실수로 변경되는 것을 방지합니다.
실무 예제: 시스템 설정값 보호
interface AppConfig {
apiKey: string;
endpoint: string;
retryCount: number;
}
const config: Readonly = {
apiKey: "SECRET_KEY_123",
endpoint: "[https://api.server.com](https://api.server.com)",
retryCount: 5
};
// Error: Cannot assign to 'apiKey' because it is a read-only property.
// config.apiKey = "NEW_KEY";
5. Record<K, T>: 키-값 쌍의 맵 구조 정의
Record는 속성 키를 K로, 속성 값을 T로 하는 타입을 만듭니다. **객체를 매핑(Mapping)**하거나 딕셔너리 구조를 만들 때 매우 강력합니다.
실무 예제: 에러 코드 메시지 매핑
type ErrorCode = "404" | "500" | "403";
const errorMessages: Record<ErrorCode, string> = {
"404": "페이지를 찾을 수 없습니다.",
"500": "서버 내부 오류가 발생했습니다.",
"403": "접근 권한이 없습니다."
};
console.log(errorMessages["404"]);
6. Required<T>: 모든 프로퍼티를 필수값으로
Partial의 반대입니다. 원래 인터페이스에 선택 사항(?)이 있었더라도, 모든 값을 강제로 입력하게 만듭니다.
실무 예제: 필수 설정 검증
interface GraphConfig {
width?: number;
height?: number;
color?: string;
}
// 모든 값이 반드시 있어야 하는 상황에서 사용
const fullConfig: Required<GraphConfig> = {
width: 100,
height: 100,
color: "blue"
};
실무 팁: 유틸리티 타입의 조합
유틸리티 타입은 중첩해서 사용할 수 있습니다.
interface Article {
id: number;
title: string;
content: string;
author: string;
createdAt: Date;
}
// '아이디'와 '작성일'은 빼고, 나머지는 선택 사항으로 만들어서 수정용 타입 생성
type UpdateArticleDto = Partial<Omit<Article, "id" | "createdAt">>;
const updateData: UpdateArticleDto = {
title: "제목만 수정합니다."
};
요약
타입역할주 활용처
| Partial | 모든 속성을 선택 사항으로 변경 | 수정 요청(Patch) |
| Pick | 특정 속성만 골라서 타입 생성 | 목록, 요약 정보 |
| Omit | 특정 속성만 빼고 타입 생성 | 민감 정보 제거 |
| Readonly | 모든 속성을 읽기 전용으로 변경 | 설정값, 상수 |
| Record | 키-값 쌍의 집합 정의 | 딕셔너리, 매핑 테이블 |
유틸리티 타입을 적절히 활용하면 중복되는 인터페이스 선언을 줄일 수 있을 뿐만 아니라, 원본 타입이 변경되었을 때 파생된 타입들도 자동으로 동기화되는 효과를 얻을 수 있습니다.