‘never’ 타입이 프로덕션에서 깨진 코드를 방지하는 방법

발행: (2026년 1월 6일 오후 04:51 GMT+9)
6 min read
원문: Dev.to

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;
  }
}

결과

Exhaustive check error screenshot

핵심 라인은:

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;

위와 같이 위험한 무시가 발생할 수 있는 곳마다 사용하세요.

Back to Blog

관련 글

더 보기 »

React 컴포넌트에서 TypeScript Generics

소개 제네릭은 React 컴포넌트에서 매일 사용하는 것은 아니지만, 특정 경우에는 유연하고 타입‑안전한 컴포넌트를 작성할 수 있게 해줍니다.