TypeScript Utility Types 실전 가이드
그래서 핵심 질문은 “어떤 유틸리티 타입을 쓸까?”보다, “이 파생 타입이 아직도 비즈니스 규칙을 정직하게 반영하고 있는가?”입니다.
유틸리티 타입이 진짜 유용한 구간
다음 같은 안정적인 변환에는 유틸리티 타입이 아주 잘 맞습니다.
- DTO에 필요한 필드만 고르기
- 부분 업데이트 payload 모델링
- 이벤트 이름과 핸들러를 매핑하기
- 기존 함수에서 parameter, return type을 끌어오기
이런 경우에는 관련 타입이 같은 source of truth에 묶여 있어서 리팩터링이 더 안전해집니다.
Partial<T>는 편하지만 위험하다
Partial<T>는 가장 많이 쓰이면서도 가장 많이 남용되는 유틸리티입니다.
잘 맞는 경우:
- patch semantics가 실제 요구사항이다
- 각 필드가 독립적으로 optional일 수 있다
- 별도 검증 계층이 이미 있다
위험한 경우:
- 어떤 필드들은 반드시 함께 와야 한다
- optional 여부가 객체 의미를 바꾼다
- 서로 다른 수정 케이스를 하나의 patch 타입으로 뭉개고 있다
많은 버그는 Partial<T>가 원래 중요했던 제약을 조용히 지워 버리면서 시작됩니다.
예시: 목적이 분명한 파생 타입
type User = {
id: string;
email: string;
name: string;
marketingOptIn: boolean;
createdAt: string;
};
type CreateUserInput = Pick<User, "email" | "name" | "marketingOptIn">;
type UpdateUserInput = Partial<Pick<User, "email" | "name" | "marketingOptIn">>;
type UserSummary = Pick<User, "id" | "email" | "name">;
이 조합이 좋은 이유는 각 타입의 역할이 분명하기 때문입니다.
- 생성 입력
- 수정 입력
- 조회 요약
독자가 파생 규칙을 거의 즉시 이해할 수 있습니다.
DTO와 도메인 모델은 같지 않다
실무에서 흔한 실수는 하나의 엔티티 타입으로 모든 계약을 기계적으로 파생하려는 것입니다.
하지만 다음 경우에는 금방 한계가 옵니다.
- 전송 payload와 도메인 불변식이 다르다
- write model과 read model이 따로 진화한다
- 권한에 따라 노출 필드가 달라진다
이때는 모든 것을 유틸리티 타입으로 해결하려 하기보다, 명시적인 별도 타입이 더 건강할 수 있습니다.
조합이 깊어질수록 가독성 예산이 필요하다
TypeScript는 아래처럼 복합 유틸리티를 허용합니다.
type SafePreview = Readonly<Partial<Omit<User, "createdAt">>>;
문제는 “가능하다”와 “좋다”가 같지 않다는 데 있습니다.
실무 기준으로는 타입 식이 별도 이름보다 해석하기 어려워지는 순간 멈추는 편이 낫습니다. 짧은 타입 수식은 도움이 되지만, 복잡한 타입 수식은 지연된 혼란이 되기 쉽습니다.
리팩터링 도구로서의 유틸리티 타입
유틸리티 타입의 가장 좋은 쓰임새 중 하나는 큰 코드베이스를 안전하게 바꾸는 것입니다.
Pick은 view model을 원본 엔티티와 정렬시키기 좋고ReturnType은 함수 결과 계약 중복을 줄여 주며Parameters는 wrapper와 decorator에 유용하고Record는 유한한 키 집합을 맵으로 명시하기 좋습니다
이런 사용은 멋져 보여서가 아니라, 변경 추적이 쉬워지기 때문에 가치가 있습니다.
자주 보는 실수
- patch 규칙을 모델링하지 않고
Partial<T>로 덮는 경우 - 분리돼야 할 DTO를 엔티티에서 무리하게 파생하는 경우
- 결과 타입을 아무도 읽지 못할 정도로 조합하는 경우
- 실제로는 키 집합이 정해져 있는데
Record<string, T>로 뭉개는 경우 - 명확한 도메인 이름보다 영리한 타입 수식을 선호하는 경우
좋은 유틸리티 타입 사용은 대체로 “조용하고 평범해 보이는” 쪽입니다. 그게 오히려 칭찬입니다.
리뷰 체크리스트
- 이 파생 타입이 여전히 비즈니스 규칙을 정직하게 반영하는가
- 명시적인 이름 있는 타입이 더 이해하기 쉽지 않은가
- update semantics가 정말 partial인가, 아니면 부분적으로만 모델링됐는가
- 어떤 원본 타입에 묶여 있어야 하는지 분명한가
- 새 팀원이 IDE 도움 없이도 대략 확장해 읽을 수 있는가
마무리 판단
유틸리티 타입은 관련 계약 사이 드리프트를 줄일 때 가장 유용합니다. 반대로 중요한 도메인 의미를 불투명한 타입 수식으로 압축하면 해롭습니다. 좋은 TypeScript 팀은 유틸리티 타입으로 모델링을 대체하지 않고, 모델링을 더 잘 유지하기 위해 유틸리티 타입을 씁니다.
Continue Reading
다음으로 읽기 좋은 글
TypeScript Generics 실전 가이드
TypeScript 제네릭을 어디서 쓰면 API 계약이 더 강해지고, 어디서부터는 타입 퍼즐이 되는지 실무 기준으로 정리합니다.
💬 LanguageModern JavaScript ES2024 문법 실전 해설
최신 JavaScript 문법을 단순 문법 소개가 아니라, 코드 품질을 실제로 높이는지 기준으로 읽습니다. 무엇이 진짜 도움이 되고 무엇은 여전히 절제가 필요한지 정리합니다.
🔧 ToolsESLint + Prettier 설정 가이드: 실전 운영 기준
React, Vue, TypeScript 프로젝트에서 ESLint와 Prettier의 역할을 분리하고, 로컬 autofix와 CI enforcement를 팀 단위로 운영하는 기준을 정리합니다.
📱 MobilePWA 완전 가이드: 앱스토어 없이 설치 가능한 웹앱
Service Worker, Web App Manifest, 오프라인 지원, 푸시 알림, 홈 화면 설치까지 포함해 PWA를 실무 기준으로 설명합니다.
다음 탐색