TypeScript

[TypeScript] 유틸리티 타입 (Utility Types)

2025년 11월 29일
1
TypeScriptFrontEndStudyUtilityTypesAdvancedTypes

📑 목차

타입스크립트는 개발자가 타입을 더 쉽고 유연하게 다룰 수 있도록 다양한 유틸리티 타입을 자체적으로 제공한다. 이는 우리가 앞서 배운 제네릭, 맵드 타입, 조건부 타입 등의 기능을 미리 조합해둔 '도구 상자'와 같다.

이번 포스팅에서는 실무에서 가장 자주 쓰이는 핵심 유틸리티 타입들을 직접 구현해 보며 그 원리를 파헤쳐 본다.

image.png


1. 맵드 타입 기반 유틸리티 타입

이들은 주로 객체 타입의 속성을 변환하는 데 사용된다.

1-1. Partial<T> (부분 집합)

특정 객체 타입의 모든 프로퍼티를 선택적(Optional) 프로퍼티로 바꿔준다.

[상황] 게시글(Post)을 작성하다가 임시 저장을 해야 한다. 아직 tags 같은 정보가 없을 수도 있다.

interface Post {
  title: string;
  tags: string[];
  content: string;
  thumbnailURL?: string;
}

// ❌ 에러: tags가 없음
// const draft: Post = { title: "제목", content: "초안" };

// ✅ 해결: Partial 사용
const draft: Partial<Post> = { 
  title: "제목", 
  content: "초안" 
};

[구현 원리] 맵드 타입을 이용해 모든 키에 ?를 붙여준다 .

type Partial<T> = {
  [key in keyof T]?: T[key];
};

1-2. Required<T> (필수 집합)

Partial과 반대로, 모든 프로퍼티를 필수(Required) 프로퍼티로 바꿔준다.

[상황] 원래는 thumbnailURL이 선택적이었지만, 마케팅용 게시글에는 썸네일이 반드시 있어야 한다.

// ✅ 해결: Required 사용 -> thumbnailURL이 없으면 에러 발생
const withThumbnail: Required<Post> = {
  title: "홍보글",
  tags: ["ts"],
  content: "내용",
  thumbnailURL: "https://..." // 필수!
};

[구현 원리] -?를 사용하여 선택적 속성(?)을 제거한다 .

type Required<T> = {
  [key in keyof T]-?: T[key];
};

1-3. Readonly<T> (읽기 전용)

모든 프로퍼티를 읽기 전용(Readonly) 으로 만들어 수정할 수 없게 한다.

[상황] 절대 수정되면 안 되는 중요한 게시글 데이터를 보호하고 싶다.

const protectedPost: Readonly<Post> = {
  title: "공지사항",
  tags: [],
  content: "수정 불가",
};

// protectedPost.content = "해킹"; // ❌ 에러 발생

[구현 원리] readonly 키워드를 붙여준다 .

type Readonly<T> = {
  readonly [key in keyof T]: T[key];
};

1-4. Pick<T, K> (골라내기)

객체 타입에서 특정 프로퍼티만 골라내어 새로운 타입을 만든다.

[상황] 태그 기능이 없던 시절의 옛날 게시글 데이터를 다뤄야 한다. tags가 없는 타입이 필요하다.

// title과 content만 뽑아냄
const legacyPost: Pick<Post, "title" | "content"> = {
  title: "옛날 글",
  content: "내용",
};

[구현 원리]KT의 키들(keyof T) 중 일부여야 한다(extends). 그 키들(K)만 순회하며 타입을 만든다 .

type Pick<T, K extends keyof T> = {
  [key in K]: T[key];
};

1-5. Omit<T, K> (제외하기)

객체 타입에서 특정 프로퍼티만 제거한 새로운 타입을 만든다. Pick의 반대다.

[상황] 제목이 없는 게시글도 존재할 수 있다. title만 뺀 타입이 필요하다.

// title만 제외함
const noTitlePost: Omit<Post, "title"> = {
  content: "내용",
  tags: [],
  thumbnailURL: "",
};

[구현 원리]PickExclude를 조합해서 만든다. 전체 키(keyof T)에서 K를 뺀 나머지 키들을 Pick하는 방식이다 .

type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;

1-6. Record<K, V> (동일한 타입 반복)

객체의 키(K)와 값(V)의 타입을 정의하여, 동일한 패턴을 가진 객체를 쉽게 만든다.

[상황] 썸네일 이미지를 크기별(large, medium, small, watch)로 관리해야 한다.

type Thumbnail = Record<"large" | "medium" | "small" | "watch", { url: string }>;

/* 결과 타입:
{
  large: { url: string },
  medium: { url: string },
  ...
}
*/

[구현 원리] 키(K)가 keyof any(string | number | symbol)를 상속받도록 하고, 모든 키에 대해 값(V)을 할당한다 .

type Record<K extends keyof any, V> = {
  [key in K]: V;
};

2. 조건부 타입 기반 유틸리티 타입

조건부 타입(extends ? :)을 이용해 타입을 걸러내거나 추출한다.

2-1. Exclude<T, U> (제거)

유니온 타입 T에서 U와 겹치는 타입을 제거한다.

type A = Exclude<string | boolean, string>; // boolean

[구현 원리]

type Exclude<T, U> = T extends U ? never : T;

2-2. Extract<T, U> (추출)

유니온 타입 T에서 U와 겹치는(할당 가능한) 타입만 추출한다.

type B = Extract<string | boolean, boolean>; // boolean

[구현 원리]

type Extract<T, U> = T extends U ? T : never;

2-3. ReturnType<T> (반환 타입 추출)

함수 타입 T반환값 타입을 추출한다.

function funcA() { return "hello"; }

type ReturnA = ReturnType<typeof funcA>; // string

[구현 원리]

infer 키워드를 사용해 반환 타입을 추론(R)한다 .

type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : never;

요약

타입스크립트가 제공하는 유틸리티 타입은 복잡한 타입을 매우 간단하게 조작할 수 있게 해준다. 직접 구현 원리를 이해하면 필요할 때 커스텀 유틸리티 타입을 만들어 쓸 수도 있으니 잘 익혀두자.

  • 변형: Partial, Required, Readonly
  • 선택/제외: Pick, Omit
  • 구조 생성: Record
  • 집합 연산: Exclude, Extract
  • 추론: ReturnType
@taemni

@taemni

안녕하세요, 차근차근 성장 중인 조태민입니다.

instagram

댓글