본문 바로가기
개발

[TS] Utility Types 정복하기: Pick, Omit, Partial로 중복 없는 타입 정의

by 돌미나리는야생미나리 2026. 4. 27.

타입스크립트(TypeScript)로 규모가 큰 프로젝트를 진행하다 보면 비슷한 형태의 타입을 반복해서 정의하게 되는 순간이 옵니다. 예를 들어, 전체 사용자 정보를 담은 User 타입이 있는데, 프로필 수정 페이지에서는 일부 정보만 필요하고, 회원가입 페이지에서는 특정 정보가 빠져야 하는 경우입니다.

이때마다 UpdateUser, RegisterUser 처럼 새로운 인터페이스를 일일이 만드는 것은 매우 비효율적입니다. 원본 타입이 수정되면 연관된 모든 타입을 찾아가서 고쳐야 하기 때문이죠. 리액트의 컴포넌트 재사용처럼, 타입스크립트에도 '타입 재사용'을 위한 강력한 도구가 있습니다. 바로 유틸리티 타입(Utility Types)입니다.


1. 왜 유틸리티 타입인가? (DRY 원칙)

개발 원칙 중 하나인 DRY(Don't Repeat Yourself, 반복하지 마라)는 타입 정의에도 적용됩니다. 유틸리티 타입을 사용하면 하나의 'Source of Truth(진실의 근원)' 타입을 두고, 이를 필요에 따라 변형하여 사용할 수 있습니다.


2. Partial<T>: 모든 속성을 선택 사항으로

Partial은 가장 자주 쓰이는 유틸리티 타입 중 하나입니다. 기존 타입의 모든 속성을 optional(?)로 바꾸어 줍니다.

2.1 실무 활용: 데이터 업데이트(Update)

프로필 수정 기능에서는 사용자가 이름만 바꿀 수도 있고, 이메일만 바꿀 수도 있습니다. 모든 필드를 보낼 필요가 없을 때 Partial이 제격입니다.

TypeScript
 
interface User {
  id: string;
  name: string;
  email: string;
  age: number;
}

// 모든 필드가 선택 사항이 됨
function updateUser(id: string, fieldsToUpdate: Partial<User>) {
  // ...업데이트 로직
}

updateUser("123", { name: "Yumina" }); // email, age가 없어도 에러가 나지 않음

3. Pick<T, K>: 필요한 것만 골라 담기

Pick은 기존 타입에서 특정 속성(Key)들만 골라내어 새로운 타입을 만듭니다.

3.1 실무 활용: 리스트 아이템 UI

사용자 상세 정보는 아주 많지만, 목록 화면(List)에서는 이름과 이메일만 보여주면 될 때 유용합니다.

TypeScript
 
// User 타입에서 'name'과 'email'만 추출
type UserSummary = Pick<User, 'name' | 'email'>;

const summary: UserSummary = {
  name: "Yumina",
  email: "dev@example.com"
};

4. Omit<T, K>: 필요 없는 것만 빼기

Omit은 Pick과 반대입니다. 특정 속성들만 제외한 나머지를 모두 가져옵니다.

4.1 실무 활용: 회원가입 시 ID 제외

DB에 저장될 때는 id가 자동으로 생성되므로, 회원가입 폼에서 입력받는 데이터 타입에는 id가 없어야 합니다.

TypeScript
 
// User 타입에서 'id'만 제외하고 나머지 모두 사용
type RegisterFormData = Omit<User, 'id'>;

const newUser: RegisterFormData = {
  name: "Yumina",
  email: "new@example.com",
  age: 25
};

5. Readonly<T>: 불변성 유지하기

객체의 속성을 수정할 수 없게 만듭니다. Redux나 Zustand 같은 상태 관리 라이브러리에서 상태의 **불변성(Immutability)**을 강제하고 싶을 때 유용합니다.

TypeScript
 
const config: Readonly<User> = {
  id: "0",
  name: "System",
  email: "admin@system.com",
  age: 99
};

// config.name = "Hack"; // 에러 발생: 읽기 전용 속성이므로 할당할 수 없습니다.

6. 고급 팁: 유틸리티 타입 조합하기

유틸리티 타입은 중첩해서 사용할 수 있습니다. 예를 들어, "업데이트 요청을 보낼 때, ID는 필수이고 나머지 정보는 선택 사항"인 타입을 만들고 싶다면 어떡할까요?

TypeScript
 
type UpdateRequest = Pick<User, 'id'> & Partial<Omit<User, 'id'>>;

const request: UpdateRequest = {
  id: "123",        // 필수
  name: "Changed"   // 선택
};

이런 식으로 조합하면 극도로 정교하고 안전한 타입 정의가 가능해집니다.