"‘문자열 하나’가 아니라 ‘허용된 바로 그 문자열’이라고 말할 수 있을 때 상태가 선명해져."
값 하나가 타입이 된다
문자열, 숫자, 불리언 리터럴은 그 값 하나만 허용하는 타입이 될 수 있어. type Direction = 'left' | 'right'는 임의의 문자열이 아니라 두 값 가운데 하나만 받는 계약이야. 오타는 컴파일 단계에서 막히고 편집기는 가능한 선택지를 자동 완성해 줘.
이 패턴은 상태, 권한, 명령 이름, 설정 단계처럼 가능한 값이 작고 닫혀 있는 곳에서 빛나. 실행 중에는 평범한 문자열이라 별도 객체나 숫자 매핑이 생기지 않아. 타입 층에서만 선택지를 제한하는 가벼운 열거형인 셈이지.
넓히기와 고정하기
const mode = 'dark'는 재할당할 수 없어 대개 리터럴 타입 'dark'로 추론돼. 객체 속성과 배열 원소는 기본적으로 더 넓게 추론될 수 있어. { mode: 'dark' } as const처럼 고정하면 속성이 읽기 전용이 되고 정확한 리터럴 정보가 남는다.
반대로 앞으로 여러 값이 들어올 변수에는 너무 좁은 리터럴 타입이 방해가 돼. let mode: 'dark' = 'dark'라고 해 두면 light로 바꿀 수 없지. 실제 상태 공간에 맞춰 리터럴 유니언을 설계해야 해.
판별 필드의 뼈대
리터럴 타입은 판별 가능한 유니언의 핵심이야. { kind: 'circle'; radius: number }와 { kind: 'square'; side: number }를 합치면 kind 값만 확인해도 컴파일러가 객체 전체의 모양을 좁힐 수 있어. 그냥 kind: string이라면 어느 변형인지 알 수 없으니 이 힘이 사라져.
값 목록에서 타입을 파생해
실행 중 순회할 값 목록과 타입 유니언을 따로 적으면 둘이 어긋날 수 있어. const MODES = ['light', 'dark'] as const를 만들고 type Mode = typeof MODES[number]로 타입을 파생하면 진실의 원천이 하나가 돼. 목록을 바꾸면 타입도 함께 바뀐다.