‘never’ 타입이 프로덕션에서 깨진 코드를 방지하는 방법
I’m happy to translate the article for you, but I’ll need the full text you’d like translated. Could you please provide the content (excluding the source line you already included)? Once I have the text, I’ll translate it into Korean while preserving the original formatting, markdown syntax, and any code blocks or URLs.
never 타입이란?
TypeScript에서 never는 일어날 수 없는 것을 나타냅니다.
이는 “아무것도 반환하지 않음”이나 “비어 있음”이 아닙니다. 도달할 수 없는 코드 경로를 의미합니다.
값의 타입이 never라면, 다음을 의미합니다:
- 가능한 런타임 값이 존재하지 않는다
- 실행이 여기까지 도달한다면, 무언가 잘못된 것이다
Simple Examples
function crash(): never {
throw new Error("Boom");
}
이 함수는 정상적으로 끝나지 않습니다.
function infiniteLoop(): never {
while (true) {}
}
실행은 절대로 그 이후로 진행될 수 없으므로 반환 타입은 never입니다.
never를 강력하게 만드는 한 가지 규칙
never는 모든 타입에 할당할 수 있지만, never 자체를 제외한 어떤 타입도 never에 할당할 수 없습니다.
let x: never;
x = "hello"; // ❌ error
x = 123; // ❌ error
TypeScript가 실제 값이 never에 할당되는 상황을 발견하면 빌드가 실패합니다. 우리는 이 규칙을 활용해 프로덕션 버그를 방지할 수 있습니다.
설정: 일반적인 React 패턴
type 필드(CMS 블록, 기능 플래그, 워크플로우 등)를 기반으로 UI 블록을 렌더링하는 컴포넌트를 상상해 보세요(매우 일반적입니다).
type Block =
| { type: "hero"; title: string }
| { type: "cta"; text: string };
렌더러
function RenderBlock({ block }: { block: Block }) {
switch (block.type) {
case "hero":
return <h2>{block.title}</h2>;
case "cta":
return <div>{block.text}</div>;
}
}
모든 것이 정상입니다:
- TypeScript가 만족합니다
- CI가 통과합니다
- 런타임 오류가 없습니다
The Production Bug (Silent)
A teammate adds a new block:
type Block =
| { type: "hero"; title: string }
| { type: "cta"; text: string }
| { type: "banner"; image: string };
They forget to update RenderBlock. What happens?
- No TypeScript error
- No build failure
- The component returns
undefined→ nothing renders
Production UI is broken silently. This is the worst kind of bug.
TypeScript가 당신을 구하지 못한 이유
TypeScript 관점에서 보면, 해당 함수는 JSX.Element | undefined를 반환할 수 있으며, 이는 기술적으로 유효합니다. TypeScript는 switch 문이 모든 경우를 포괄한다고 가정하지 않기 때문에 버그가 통과됩니다.
잘못된 “수정”
이와 같이 default 케이스를 추가하면:
default:
return null;
문제는 여전히 남아 있습니다:
- 컴파일러 오류가 없음
- UI가 여전히 깨짐
- 문제를 발견하기 어려워짐
우리는 TypeScript가 강력히 실패하도록 해야 합니다.
never 소개
컴포넌트에서 한 줄을 변경합니다:
function RenderBlock({ block }: { block: Block }) {
switch (block.type) {
case "hero":
return <h2>{block.title}</h2>;
case "cta":
return <div>{block.text}</div>;
default:
const _exhaustiveCheck: never = block;
return null;
}
}
결과

핵심 라인은:
const _exhaustiveCheck: never = block;
무엇이 변경되었나요?
이 줄은 TypeScript에 “block이 여기까지 도달한다면 코드는 잘못된 것이다.”라고 알려줍니다.
banner가 존재할 때, TypeScript은 block이 { type: "banner" }일 수 있음을 인식하고, 이는 never에 할당할 수 없음을 의미하여 컴파일‑타임 오류를 발생시킵니다. CI가 실패하고, 버그가 배포되지 않습니다.
진정한 승리
It’s not just about exhaustiveness checking; it provides the guarantee:
If the code compiles, all cases are handled.
That’s production safety.
실제 코드베이스에서 이것이 중요한 이유
패턴은 다음과 같은 경우에 빛을 발합니다:
- CMS 스키마가 진화할 때
- 여러 팀이 동일한 유니온 타입을 다룰 때
- UI 렌더링이 설정이나 API 응답에 의존할 때
- 리듀서나 워크플로가 시간이 지남에 따라 커질 때
어디에서든 types change faster than implementations, never가 여러분을 보호합니다.
정신 모델
never는 다음을 의미합니다: “이 코드 경로는 존재해서는 안 됩니다.”
만약 TypeScript가 그것이 존재할 수 있다는 것을 증명한다면, 빌드가 실패합니다 — 설계상 그렇게 됩니다.
결론
- TypeScript는 기본적으로 전체 경우를 보장하지 않습니다.
- React 컴포넌트는 프로덕션 환경에서 조용히 깨질 수 있습니다.
never는 누락된 경우를 컴파일러 오류로 변환합니다.
한 줄이 전체 버그 유형을 방지합니다:
const _exhaustiveCheck: never = value;
위와 같이 위험한 무시가 발생할 수 있는 곳마다 사용하세요.