[TypeScript] 유틸리티 타입 (Utility Types)
타입스크립트는 개발자가 타입을 더 쉽고 유연하게 다룰 수 있도록 다양한 유틸리티 타입을 자체적으로 제공한다. 이는 우리가 앞서 배운 제네릭, 맵드 타입, 조건부 타입 등의 기능을 미리 조합해둔 '도구 상자'와 같다. 이번 포스팅에서는 실무에서 가장 자주 쓰이는 핵심 유틸리티 타입들을 직접 구현해 보며 그 원리를 파헤쳐 본다.  --- 1. 맵드 타입 기반 유틸리티 타입 이들은 주로 객체 타입의 속성을 변환하는 데 사용된다. 1-1. Partial<T> (부분 집합) 특정 객체 타입의 모든 프로퍼티를 선택적(Optional) 프로퍼티로 바꿔준다. [상황] 게시글(Post)을 작성하다가 임시 저장을 해야 한다. 아직 tags 같은 정보가 없을 수도 있다. TypeScript interface Post { title: string; tags: string[]; content: string; thumbnailURL?: string; } // ❌ 에러: tags가 없음 // const draft: Post = { title: "제목", content: "초안" }; // ✅ 해결: Partial 사용 const draft: Partial<Post> = { title: "제목", content: "초안" }; [구현 원리] 맵드 타입을 이용해 모든 키에 ?를 붙여준다 . TypeScript type Partial<T> = { [key in keyof T]?: T[key]; }; 1-2. Required<T> (필수 집합) Partial과 반대로, 모든 프로퍼티를 필수(Required) 프로퍼티로 바꿔준다. [상황] 원래는 thumbnailURL이 선택적이었지만, 마케팅용 게시글에는 썸네일이 반드시 있어야 한다. TypeScript // ✅ 해결: Required 사용 -> thumbnailURL이 없으면 에러 발생 const withThumbnail: Required<Post> = { title: "홍보글", tags: ["ts"], content: "내용", thumbnailURL: "https://..." // 필수! }; [구현 원리] -?를 사용하여 선택적 속성(?)을 제거한다 . TypeScript type Required<T> = { [key in keyof T]-?: T[key]; }; 1-3. Readonly<T> (읽기 전용) 모든 프로퍼티를 읽기 전용(Readonly) 으로 만들어 수정할 수 없게 한다. [상황] 절대 수정되면 안 되는 중요한 게시글 데이터를 보호하고 싶다. TypeScript const protectedPost: Readonly<Post> = { title: "공지사항", tags: [], content: "수정 불가", }; // protectedPost.content = "해킹"; // ❌ 에러 발생 [구현 원리] readonly 키워드를 붙여준다 . TypeScript type Readonly<T> = { readonly [key in keyof T]: T[key]; }; 1-4. Pick<T, K> (골라내기) 객체 타입에서 특정 프로퍼티만 골라내어 새로운 타입을 만든다. [상황] 태그 기능이 없던 시절의 옛날 게시글 데이터를 다뤄야 한다. tags가 없는 타입이 필요하다. TypeScript // title과 content만 뽑아냄 const legacyPost: Pick<Post, "title" | "content"> = { title: "옛날 글", content: "내용", }; [구현 원리]K는 T의 키들(keyof T) 중 일부여야 한다(extends). 그 키들(K)만 순회하며 타입을 만든다 . TypeScript type Pick<T, K extends keyof T> = { [key in K]: T[key]; }; 1-5. Omit<T, K> (제외하기) 객체 타입에서 특정 프로퍼티만 제거한 새로운 타입을 만든다. Pick의 반대다. [상황] 제목이 없는 게시글도 존재할 수 있다. title만 뺀 타입이 필요하다. TypeScript // title만 제외함 const noTitlePost: Omit<Post, "title"> = { content: "내용", tags: [], thumbnailURL: "", }; [구현 원리]Pick과 Exclude를 조합해서 만든다. 전체 키(keyof T)에서 K를 뺀 나머지 키들을 Pick하는 방식이다 . TypeScript type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>; 1-6. Record<K, V> (동일한 타입 반복) 객체의 키(K)와 값(V)의 타입을 정의하여, 동일한 패턴을 가진 객체를 쉽게 만든다. [상황] 썸네일 이미지를 크기별(large, medium, small, watch)로 관리해야 한다. TypeScript type Thumbnail = Record<"large" | "medium" | "small" | "watch", { url: string }>; / 결과 타입: { large: { url: string }, medium: { url: string }, ... } / [구현 원리] 키(K)가 keyof any(string | number | symbol)를 상속받도록 하고, 모든 키에 대해 값(V)을 할당한다 . TypeScript type Record<K extends keyof any, V> = { [key in K]: V; }; --- 2. 조건부 타입 기반 유틸리티 타입 조건부 타입(extends ? :)을 이용해 타입을 걸러내거나 추출한다. 2-1. Exclude<T, U> (제거) 유니온 타입 T에서 U와 겹치는 타입을 제거한다. TypeScript type A = Exclude<string | boolean, string>; // boolean [구현 원리] TypeScript type Exclude<T, U> = T extends U ? never : T; 2-2. Extract<T, U> (추출) 유니온 타입 T에서 U와 겹치는(할당 가능한) 타입만 추출한다. TypeScript type B = Extract<string | boolean, boolean>; // boolean [구현 원리] TypeScript type Extract<T, U> = T extends U ? T : never; 2-3. ReturnType<T> (반환 타입 추출) 함수 타입 T의 반환값 타입을 추출한다. TypeScript function funcA() { return "hello"; } type ReturnA = ReturnType<typeof funcA>; // string [구현 원리] infer 키워드를 사용해 반환 타입을 추론(R)한다 . TypeScript type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : never; --- 요약 타입스크립트가 제공하는 유틸리티 타입은 복잡한 타입을 매우 간단하게 조작할 수 있게 해준다. 직접 구현 원리를 이해하면 필요할 때 커스텀 유틸리티 타입을 만들어 쓸 수도 있으니 잘 익혀두자. - 변형: Partial, Required, Readonly - 선택/제외: Pick, Omit - 구조 생성: Record - 집합 연산: Exclude, Extract - 추론: ReturnType
