숏컷 코드
type Status = "idle" | "loading" | "success" | "error";
function setStatus(status: Status) {
return status;
}합집합과 리터럴
먼저 자주 보는 형태를 한 번에 보면 아래와 같습니다.
- 여러 타입 중 하나:
string | number - 값 후보가 고정된 상태:
"idle" | "loading" | "done" - 객체 상태를 값으로 구분:
{ kind: "circle" } | { kind: "rect" } - 값 배열에서 literal union 만들기:
typeof states[number]
1) 여러 타입 중 하나일 수 있으면 union
string | number는 값이 둘 중 하나라는 뜻입니다.
즉 TypeScript의 union은 JavaScript의 유연함을 버리지 않고, 가능한 경우의 수를 타입으로 드러냅니다.
function printId(id: string | number) {
console.log(id);
}2) 가능한 값 자체를 고정하면 literal union
아무 문자열이나 허용하는 대신 "light" | "dark"처럼 값 집합을 고정하면 상태와 옵션 설계가 더 단단해집니다.
type Theme = "light" | "dark";
function setTheme(theme: Theme) {
return theme;
}이 패턴은 오타 방지, 자동완성, 리팩터링 안정성까지 같이 얻는 방식입니다.
function setThemeLoose(theme: string) {
return theme;
}
function setThemeStrict(theme: "light" | "dark") {
return theme;
}아무 문자열을 받을지, 가능한 상태를 몇 개로 고정할지는 설계 의도 차이입니다.
3) union을 선언하면 곧 narrowing 전략이 따라온다
union 값은 각 멤버 전용 메서드를 바로 쓸 수 없습니다. 실제 사용 위치에서는 지금 어떤 경우인지 좁히는 과정이 뒤따릅니다.
function printId(id: string | number) {
if (typeof id === "string") {
console.log(id.toUpperCase());
} else {
console.log(id.toFixed(0));
}
}즉 union 카드는 바로 다음 narrowing 카드와 붙어서 읽는 편이 자연스럽습니다.
4) 상태와 모드 설계에 literal union이 특히 강하다
UI 상태, 요청 상태, 정렬 방향, permission 레벨처럼 값 후보가 몇 개로 정해진 경우에는 enum보다 먼저 literal union을 보는 편이 가볍고 읽기 쉽습니다.
const states = ["idle", "loading", "done"] as const;
type State = typeof states[number];5) 너무 넓은 union은 오히려 사용 코드를 무겁게 만든다
가능한 경우를 잘라 담는 것이 union의 장점이지만, 경우의 수를 무리하게 한 타입에 몰아넣으면 분기 비용이 커질 수 있습니다. 즉 union은 "정확한 제한"이 목적이지 "모든 경우를 한 타입에 우겨 넣기"가 목적은 아닙니다.
type Theme = "light" | "dark";
type AnyTheme = string;
function setThemeStrict(theme: Theme) {}
function setThemeLoose(theme: AnyTheme) {}literal union은 자유도를 줄이는 대신 호출 계약을 선명하게 만듭니다. 옵션이 몇 개로 고정된 순간에는 넓은 string보다 literal union이 더 강합니다.
언제 좁혀 쓸까
union을 설계할 때 볼 점
- 여러 타입 가능성을 표현한다: union
A | B - 가능한 값 집합을 제한한다: literal union
- 상태/모드/API 옵션 설계다: literal union 우선
- union 값을 실제로 사용한다: narrowing 필요
- 아무 문자열이나 받으면 된다:
string
주의할 점
union을 선언했다고 해서 멤버 전용 메서드를 바로 쓸 수는 없습니다. 공통 부분만 바로 접근할 수 있고, 나머지는 먼저 좁혀야 합니다.
// ❌ string 전용 메서드를 바로 호출
function upper(value: string | number) {
return value.toUpperCase();
}
// ✅ 먼저 타입을 좁힘
function upper(value: string | number) {
return typeof value === "string" ? value.toUpperCase() : String(value);
}
// ❌ 너무 넓게 두면 실수 상태까지 같이 들어옴
type ThemeLoose = string;
// ✅ 실제 허용 상태를 고정
type ThemeStrict = "light" | "dark";// ❌ literal union인데도 값을 너무 일찍 string으로 넓혀 버리면 이점이 줄어든다
let theme = "light";
setTheme(theme);
// ✅ 리터럴 값을 유지하거나 타입을 직접 고정한다
const strictTheme: "light" | "dark" = "light";
setTheme(strictTheme);참고 링크
1 sources