코드 짜다 보면 이런 생각이 한 번씩 든다.
- "User 타입 전체는 너무 크고"
- "지금 이 화면에서는 name이랑 email만 있으면 되는데"
- "타입을 새로 만들자니 귀찮고, any 쓰자니 찝찝하고"
이럴 때 쓰는 게 Pick<T, K>다.
1. Pick<T, K>를 한 줄로 설명하면
Pick<T, K>는
어떤 객체 타입 T에서
필요한 속성 이름 K만 골라서
새로운 타입을 만드는 도구다.
생각보다 단순하다.
2. 기본 사용법부터 보기
먼저 예제로 감 잡고 나중에 정의를 보자.
type User = {
id: number
name: string
email: string
isAdmin: boolean
}
// 프로필 화면에서 필요한 정보만
type UserProfile = Pick<User, "id" | "name">
const profile: UserProfile = {
id: 1,
name: "홍길동"
// email, isAdmin은 없어도 된다
}
여기서 알 수 있는 점
User는 그대로 두고UserProfile은User안에서"id" | "name"만 골라서 만든 타입User에 필드가 추가되면UserProfile도 타입 체크를 같이 받는다"iddd"처럼 오타로 적으면 컴파일 단계에서 바로 에러가 난다
3. Pick<T, K>가 실제로 뭔지 살짝만 들여다보기
참고용으로 공식 정의는 이런 모양이다.
type Pick<T, K extends keyof T> = {
[P in K]: T[P]
}
이걸 풀어서 보면
T에 있는 key들 중에서K에 해당하는 key만 돌면서T에서 그 key의 타입을 그대로 가져온다
여기서 중요한 포인트는 두 가지다.
K는 반드시T안에 존재하는 키여야 한다."age"를 골라 쓰려면User안에 진짜age가 있어야 한다.- 이건 런타임 동작이 아니라 타입 시스템에서만 도는 논리다.
실제로 JS 객체에서 필드가 삭제되거나 하지는 않는다.
4. 실무에서 언제 쓰면 좋은지
4-1. 리스트용 요약 데이터 만들 때
목록 화면은 보통 전체 데이터가 다 필요 없다.
type Product = {
id: string
name: string
price: number
description: string
createdAt: string
}
// 리스트 카드에 필요한 필드만
type ProductListItem = Pick<Product, "id" | "name" | "price">
이렇게 해 두면
- 리스트 컴포넌트는 Product 전체를 요구하지 않고
- 진짜로 사용하는 필드만 Props로 받게 된다.
type ProductCardProps = {
product: ProductListItem
}
4-2. 폼용 타입 분리할 때
회원가입, 설정, 수정 같은 폼은 DB 전체 스키마랑 1:1로 맞출 필요가 없다.
type User = {
id: number
name: string
email: string
password: string
isAdmin: boolean
}
// 가입 폼에 실제로 입력 받는 값만
type SignUpFormValues = Pick<User, "name" | "email" | "password">
장점은 이런 느낌이다.
- 나중에
User에 필드가 늘어나도
폼에 불필요한 필드가 갑자기 끼어들지 않는다. - 폼 검증 스키마(Zod 등)와 타입을 맞추기도 편하다.
4-3. API 응답에서 필요한 필드만 흘려보낼 때
백엔드에서 내려오는 타입이 꽤 크고 복잡한 경우,
레이어별로 필요한 범위만 줄여서 쓰는 데도 잘 맞는다.
// API에서 내려온 전체 타입
type UserDTO = {
id: number
name: string
email: string
phone: string
address: string
createdAt: string
// ...
}
// 헤더 프로필 드롭다운에 필요한 정보만
type HeaderUser = Pick<UserDTO, "id" | "name" | "email">
function HeaderUserMenu(props: { user: HeaderUser }) {
// 여기서는 name, email 정도만 쓰는 상황
}
의도를 타입으로 표현하는 느낌이라고 보면 된다.
이 컴포넌트는 User 데이터 중에 “이 정도 정보만 관심 있다”
라는 걸 타입 자체에 박아두는 것
5. Pick을 막 쓰기 전에 생각해 볼 것
유틸리티 타입은 편해서 막 쓰기 좋은데,
너무 막 쓰면 오히려 읽기 힘들어지는 경우도 생긴다.
5-1. "이제 거의 다른 타입인데?" 싶은 경우
원본 타입과 의미가 너무 달라지면
아예 새 타입 이름을 정해서 만드는 편이 더 낫다.
// 애매한 예
type UserListRow = Pick<User, "id" | "name" | "createdAt">
// 차라리
type UserListRow = {
id: number
name: string
joinedAt: string
}
이미 도메인에서 완전히 다른 의미라면Pick으로 끌어다 쓰는 대신 타입을 분리하는 것도 고려할 만하다.
5-2. 중첩 Pick / 중첩 유틸리티 남발
type Something = Partial<Pick<User, "name" | "email">>
이 정도는 괜찮은데,
실무에서 가끔 이런 것도 본다.
type X = Partial<
Pick<
SomeBigType,
"fieldA" | "fieldB" | "fieldC" | "fieldD"
>
>
이게 한두 번이면 괜찮지만,
여러 군데에서 이렇게 쓰이면 읽는 사람이 해석하기가 점점 힘들어진다.
그럴 때는
- 중간에 한 번 타입을 이름 붙여서 끊거나
- 아예 도메인에 맞는 새 타입을 선언하는 편이 나을 때도 있다.
5-3. 런타임에서 필드가 실제로 제거되는 건 아니다
Pick은 타입 단계에서만 작동한다.
type UserProfile = Pick<User, "id" | "name">
declare const user: User
declare const profile: UserProfile
이렇게 되어 있어도 런타임에서는user 객체가 그대로 돌아다닌다.
- 네트워크로 보낼 때
- 로깅할 때
실제로 필드를 빼고 싶다면
타입이 아니라 데이터 처리 로직이 별도로 필요하다.
6. 정리 – Pick을 언제 떠올리면 되는가
짧게 정리하면 이렇게 쓰면 좋다.
- 기존 타입에서 일부 필드만 안전하게 써야 할 때
- 컴포넌트가 정말로 사용하는 필드만 Props로 받고 싶을 때
- 폼, 목록, 카드 등에서 요약 정보 타입이 필요할 때
- 원본 타입이 바뀌면 관련 타입도 자동으로 같이 체크 받고 싶을 때
반대로 이런 경우에는 한 번 더 생각해 보면 좋다.
- 의미가 거의 다른 타입인데 억지로 Pick으로 이어 붙이고 있지는 않은지
- 유틸리티 타입이 너무 중첩되어서
읽는 사람이 해석하기 어렵지 않은지
'FrontEnd > TypeScript' 카테고리의 다른 글
| TypeScript as const 이게 뭘까 (0) | 2025.12.18 |
|---|---|
| TypeScript 유틸리티 타입 Partial<T> – 부분만 채워도 되는 타입 만들기 (1) | 2025.12.14 |
| TypeScript 유틸리티 타입 Omit<T, K> – 필요 없는 필드만 쏙 빼고 쓰기 (0) | 2025.12.14 |
| 타입연산자 typeof, keyof, keyof typeof 패턴 정리 (0) | 2025.12.14 |
| setTimeout() 반환값 정리 – 브라우저 vs Node에서 뭐가 다른지 (0) | 2024.11.24 |
