"타입은 카테고리 아냐. 받아들일 정확한 값 집합이야. `'red'` 자체가 완벽하게 좋은 타입."
사고 전환
대부분 프로그래머가 타입이 카테고리인 정적 타입 언어로 타이핑 배워 — "이건 number," "이건 string." TypeScript 도 그거 할 수 있지만, 더 강한 것도 할 수 있어: 타입이 단일 값일 수 있어. type Status = 'idle' 은 정확히 한 값, 문자열 `'idle'` 만 담는 타입. 다른 거 다 실패.
Literal 타입을 union 과 결합하면 enum 같은 거 — 근데 더 유연하고 compile 시점에 erased. type Status = 'idle' | 'loading' | 'success' | 'error' 는 status 필드의 허용 값 정확한 집합 묘사. 다른 거 전달하면 compiler 가 막아.
Inference 가 literal vs widened 고르는 법
TypeScript 가 literal 타입 줄지 widened parent 줄지 결정하는 규칙:
const x = 'hi'는x: 'hi'추론 (literal — `const` 못 바뀜).let x = 'hi'는x: string추론 (widened — `let` 은 어떤 string 도 될 수).const x: 'hi' = 'hi'명시 annotate — widening 안 됐을 거지만 literal 타입 유지.const arr = ['hi']는arr: string[]추론 (배열 element 는 default 로 widening).const arr = ['hi'] as const는arr: readonly ['hi']추론 (as const가 widening 과 mutability 둘 다 죽임).
이 widening 행동이 as const 가 존재하는 이유 — TypeScript 가 그러지 않으면 generalize 할 곳에서 literal 타입 유지하는 override knob.
Literal 타입 3가지 카테고리
String literal: type Color = 'red' | 'green' | 'blue'. 가장 흔한 형태. Status 필드, tag 이름, union 의 kind discriminator 에 쓰여.
Number literal: type Quadrant = 1 | 2 | 3 | 4. 덜 흔하지만 제한된 숫자 도메인에 유용. HTTP status code 가 자연스러운 적합: type Status = 200 | 404 | 500.
Boolean literal: type True = true. 단독으론 드물지만, conditional type 과 nail down 하고 싶은 flag 에 나타나.
Literal 타입이 narrowing 을 강하게 만들어
Literal 타입을 일찍 배우는 가장 큰 이유: 나중에 narrowing 트랙이 명백하게 느껴지게 만들어. `status` 가 `'idle' | 'loading' | 'done'` 타입일 때, if (status === 'idle') 가능하고 그 분기 안에서 TypeScript 가 `status` 는 literal `'idle'` 이라고 알아 — 그리고 다른 분기는 남은 멤버로 narrow. 이게 discriminated union, exhaustive switch, 트랙 6 과 7 의 대부분 패턴의 기초.
피파의 고백
'claude' | 'codex' | 'gemini' | 'ollama'. `ReasoningLevel` 은 'minimal' | 'low' | 'medium' | 'high' | 'ultra'. 모든 async hook 의 status 필드는 literal union. 어느 것도 enum 아냐 — 그냥 literal-type union, compile 시점에 erase 돼서 runtime 은 plain string 봐. Type system 이 constraint 잡고, runtime 은 가벼움 유지.