"이미 잘 만든 값이 있다면, 그 모양을 타입으로 다시 받아 적지 마."
같은 철자, 다른 시간대
값 표현식의 typeof x는 실행 중에 'string' 같은 문자열을 돌려줘. 반면 타입 위치의 typeof x는 컴파일러가 알고 있는 x의 정적 타입을 가져와. const user = { id: 1, name: 'Pippa' } 다음에 type User = typeof user라고 쓰면 User는 그 객체 모양을 그대로 따른다.
두 연산자는 철자가 같아도 목적이 달라. 하나는 런타임 분기용이고, 다른 하나는 타입 선언을 값과 동기화하기 위한 도구야. 코드를 읽을 때 typeof가 값 자리에 있는지, type이나 제네릭 인자 같은 타입 자리에 있는지 먼저 보면 헷갈리지 않아.
값을 기준 원본으로 삼기
경로표나 설정처럼 실제 실행에 쓰이는 상수가 먼저라면 typeof가 특히 좋아. const ROUTES = { home: '/', chat: '/chat' } as const에서 type Routes = typeof ROUTES를 만들면 값과 타입이 한 선언에서 출발해. 키만 필요하면 keyof typeof ROUTES, 경로 값만 필요하면 (typeof ROUTES)[keyof typeof ROUTES]까지 이어갈 수 있지.
여기서 as const가 없으면 문자열 값이 일반 string으로 넓어질 수 있어. typeof는 선언된 값보다 더 정밀한 정보를 발명하지 않아. 리터럴 자체가 의미 있는 API라면 먼저 값의 추론 폭을 적절히 고정해야 해.
함수와 라이브러리에서 재사용하기
함수도 값이므로 typeof fetchUser로 함수 타입을 얻을 수 있어. 여기에 ReturnType<typeof fetchUser>나 Parameters<typeof fetchUser>를 붙이면 이미 구현된 함수에서 결과와 인자 타입을 파생한다. 이름 없는 콜백의 모양을 억지로 복사하기보다 원본 함수와 연결하는 편이 변경에도 강해.
다만 공개 계약이 구현 세부에 끌려가면 곤란할 수 있어. 도메인의 안정된 경계를 표현하는 타입은 명시적으로 이름 붙이는 편이 낫고, 구현과 함께 움직여야 하는 보조 타입은 typeof로 파생하는 편이 좋아. 파생 가능하다는 이유만으로 모든 타입을 파생할 필요는 없어.
typeof는 값과 타입의 중복 선언을 없애는 다리야. 무엇이 원본이어야 하는지 먼저 정하고, 값이 원본인 곳에서만 이 다리를 건너.