우아한 타입스크립트 스터디 5장 - 타입 활용하기
우아한 타입스크립트 스터디 5장 - 타입 활용하기
개요
5장은 타입스크립트의 고급 기능을 실무에 활용하는 방법을 다룹니다. 우아한형제들(배달의민족)의 실제 개발 사례를 통해 조건부 타입, 템플릿 리터럴, 커스텀 유틸리티 타입 등 실무에서 유용한 타입 활용법을 소개합니다.
5.1 조건부 타입 (Conditional Types)
5.1.1 extends와 제네릭을 활용한 조건부 타입
기본 문법: T extends U ? X : Y
- 타입 T를 U에 할당할 수 있으면 X 타입, 아니면 Y 타입으로 결정됩니다.
interface Bank {
financialCode: string
companyName: string
name: string
fullName: string
}
interface Card {
financialCode: string
companyName: string
name: string
appCardType: string
}
type PayMethod<T> = T extends "card" ? Card : Bank
type CardPayMethod = PayMethod<"card"> // Card 타입
type BankPayMethod = PayMethod<"bank"> // Bank 타입
장점:
- 제네릭과 extends를 함께 사용해 타입을 제한하여 잘못된 값 방지
- 조건부 타입으로 반환 값을 구체화하여 불필요한 타입 가드, 타입 단언 방지
5.1.2 infer를 활용한 타입 추론
extends와 함께 infer 키워드를 사용하여 타입을 추론할 수 있습니다.
// Promise 배열에서 내부 타입 추론
type UnpackPromiseArray<T> = T extends Promise<infer K>[] ? K : any
const promises = [Promise.resolve("Mark"), Promise.resolve(38)]
type Expected = UnpackPromiseArray<typeof promises> // string | number
5.2 템플릿 리터럴 타입 활용하기
자바스크립트의 템플릿 리터럴 문법을 사용해 특정 문자열 패턴에 대한 타입을 선언할 수 있습니다.
type HeadingNumber = 1 | 2 | 3 | 4 | 5
type HeaderTag = `h${HeadingNumber}` // "h1" | "h2" | "h3" | "h4" | "h5"
주의사항:
- 유니온 조합의 경우의 수가 너무 많으면 타입스크립트 컴파일러 성능에 영향
- 적절하게 나누어 타입을 정의하는 것이 좋음
5.3 커스텀 유틸리티 타입 활용하기
5.3.1 styled-components의 중복 타입 선언 피하기
Pick 유틸리티 타입을 활용해 Props에서 필요한 부분만 선택하여 styled-components 타입 정의:
// HrComponent.tsx
export type Props = {
height?: string;
color?: keyof typeof colors;
isFull?: boolean;
className?: string;
// ...
};
export const Hr: VFC<Props> = ({height, color, isFull, className}) => {
return <HrComponent ... />;
};
// style.ts
import {Props} from "...";
type StyledProps = Pick<Props, "height" | "color" | "isFull">;
const HrComponent = styled.hr<StyledProps>`
// ...
`;
5.3.2 PickOne 유틸리티 함수
서로 다른 객체를 유니온 타입으로 받을 때의 타입 검사 문제를 해결하는 커스텀 유틸리티 타입:
문제 상황:
type Card = { card: string }
type Account = { account: string }
function withdraw(type: Card | Account) {
/* */
}
// 타입 에러가 발생하지 않는 문제
withdraw({ card: "hyundai", account: "hana" })
PickOne 구현:
type PickOne<T> = {
[P in keyof T]: Record<P, T[P]> &
Partial<Record<Exclude<keyof T, P>, undefined>>
}[keyof T]
// 사용 예시
type PayMethodType = PickOne<Card & Account>
// { card: string; account?: undefined } | { card?: undefined; account: string }
5.3.3 NonNullable 타입 검사 함수
null이나 undefined를 안전하게 처리하는 타입 가드 함수:
function NonNullable<T>(value: T): value is NonNullable<T> {
return value !== null && value !== undefined
}
// 사용 예시
const values = [1, 2, null, 4, undefined]
const validValues = values.filter(NonNullable) // number[]
5.4 불변 객체 타입으로 활용하기
5.4.1 Atom 컴포넌트에서 theme style 객체 활용
as const와 keyof 활용:
const colors = {
red: "#F45452",
green: "#0C952A",
blue: "#1A7CFF",
} as const
const getColorHex = (key: keyof typeof colors) => colors[key]
// 자동완성과 타입 안전성 확보
getColorHex("red") // ✅ 정상
getColorHex("yellow") // ❌ 타입 에러
keyof 연산자:
interface ColorType {
red: string
green: string
blue: string
}
type ColorKeyType = keyof ColorType // 'red' | 'green' | 'blue'
typeof 연산자:
const colors = {
red: "#F45452",
green: "#0C952A",
blue: "#1A7CFF",
}
type ColorsType = typeof colors
/*
{
red: string;
green: string;
blue: string;
}
*/
5.5 Record 원시 타입 키 개선하기
5.5.1 무한한 키를 가지는 Record의 문제점
type Category = string
interface Food {
name: string
}
const foodByCategory: Record<Category, Food[]> = {
한식: [{ name: "김치" }, { name: "된장찌개" }],
일식: [{ name: "초밥" }, { name: "텐동" }],
}
// 런타임 에러 발생 가능
foodByCategory["양식"].map((food) => console.log(food)) // undefined.map() 에러
5.5.2 유닛 타입으로 변경
type Category = "한식" | "일식" // 유한한 집합으로 제한
const foodByCategory: Record<Category, Food[]> = {
한식: [{ name: "김치" }, { name: "된장찌개" }],
일식: [{ name: "초밥" }, { name: "텐동" }],
}
5.5.3 Partial을 활용한 정확한 타입 표현
키가 무한한 상황에서 Partial을 사용하여 undefined 가능성 표현:
type PartialRecord<K extends string, T> = Partial<Record<K, T>>
type Category = string
interface Food {
name: string
}
const foodByCategory: PartialRecord<Category, Food[]> = {
한식: [{ name: "김치" }, { name: "된장찌개" }],
일식: [{ name: "초밥" }, { name: "텐동" }],
}
foodByCategory["양식"] // Food[] | undefined 타입으로 추론
핵심 포인트
- 조건부 타입으로 타입 안전성과 코드 간결성 확보
- infer 키워드로 복잡한 타입에서 필요한 부분만 추론
- 템플릿 리터럴 타입으로 문자열 패턴 타입 안전성 확보
- 커스텀 유틸리티 타입으로 반복되는 타입 패턴 해결
- 불변 객체와 keyof/typeof 조합으로 런타임 안전성 확보
- Record 타입 개선으로 undefined 처리 명확화
실무 적용 팁
- 타입 가드 함수를 적극 활용하여 런타임 안전성 확보
- Pick, Omit 등 유틸리티 타입으로 코드 중복 제거
- as const와 keyof 조합으로 자동완성과 타입 안전성 동시 확보
- PartialRecord 패턴으로 동적 키 상황에서 안전한 타입 설계