🔀
판별 유니언 — TypeScript에서 enum 대신 쓰는 패턴
type 필드로 분기하면 TypeScript가 각 분기의 타입을 자동으로 좁혀준다
type Shape =
| { kind: 'circle'; radius: number }
| { kind: 'rectangle'; width: number; height: number }
| { kind: 'triangle'; base: number; height: number };
function area(shape: Shape): number {
switch (shape.kind) {
case 'circle': return Math.PI * shape.radius ** 2; // shape.radius 접근 가능
case 'rectangle': return shape.width * shape.height; // shape.width 접근 가능
case 'triangle': return 0.5 * shape.base * shape.height;
}
}
shape.kind로 분기하면 TypeScript가 각 case 안에서 shape의 타입을 자동으로 좁혀준다. circle case에서는 shape.radius에 접근할 수 있고, rectangle case에서는 shape.width에 접근할 수 있다.
왜 enum 대신 쓰는가
enum은 런타임에 코드가 생성된다 (역방향 매핑 객체). 판별 유니언은 타입 레벨에서만 존재하고 런타임 코드가 0이다.
// enum — 런타임에 객체가 생성됨
enum Direction { Up, Down } // Direction[0] === 'Up' 역매핑
// 판별 유니언 — 런타임 비용 0
type Direction = 'up' | 'down';
exhaustive check
function area(shape: Shape): number {
switch (shape.kind) {
case 'circle': return ...;
case 'rectangle': return ...;
// 'triangle' 빠뜨리면?
default:
const _exhaustive: never = shape; // 컴파일 에러! triangle이 never에 안 맞음
return _exhaustive;
}
}
never 타입으로 모든 케이스를 처리했는지 컴파일 타임에 검증할 수 있다. Rust의 exhaustive match와 같은 효과.
핵심 포인트
1
유니언의 각 타입에 공통 리터럴 필드(kind, type 등)를 둔다 = discriminant
2
switch/if로 discriminant를 분기하면 TypeScript가 자동 타입 좁히기
3
never 타입으로 exhaustive check — 케이스 누락 시 컴파일 에러
4
enum보다 런타임 비용 0 + 타입 안전성 동일 (또는 더 좋음)
사용 사례
Redux action — { type: "INCREMENT" } | { type: "SET_VALUE", payload: number }
API 응답 — { status: "success", data: T } | { status: "error", message: string }