SHIN STORYSHIN STORY
홈포스트C#TypeScriptNext.jsNode.js시리즈
</>SHIN STORY

sdf

탐색

  • 홈
  • 모든 포스트
  • 시리즈
  • 검색

카테고리

  • C#
  • TypeScript
  • Next.js
  • Node.js
  • 알고리즘
  • 개발 도구

© 2025 Shin Blog. All rights reserved.

GitHubRSS
목록으로
TypeScript#TypeScript#React

TypeScript 팁 #3 — 판별 유니온으로 타입 안전한 상태 관리

SHIN

2025년 4월 19일2분 읽기0
📚

TypeScript 실전 팁 10선

10편
  1. 5TypeScript 팁 #5 — 타입 가드로 런타임 타입 좁히기
  2. 6TypeScript 팁 #6 — 매핑 타입으로 타입 변환하기
  3. 10TypeScript 팁 #1 — strict 모드를 반드시 켜야 하는 이유
  4. 10TypeScript 팁 #2 — 유틸리티 타입 완전 정복
  5. TypeScript 팁 #3 — 판별 유니온으로 타입 안전한 상태 관리현재
  6. 10TypeScript 팁 #4 — 제네릭 제약 조건과 infer 활용
  7. 10TypeScript 팁 #7 — 템플릿 리터럴 타입으로 문자열 타입 조작
  8. 10TypeScript 팁 #8 — 선언 병합과 모듈 보강으로 타입 확장하기
  9. 10TypeScript 팁 #9 — 타입 안전한 에러 처리 패턴
  10. 10TypeScript 팁 #10 — 타입 성능 최적화와 실무 패턴

TypeScript 팁 #3 — 판별 유니온으로 타입 안전한 상태 관리

문제: 느슨한 타입의 상태 객체

CODE
// ❌ 어떤 필드가 언제 존재하는지 불명확
interface FetchState {
  status: 'idle' | 'loading' | 'success' | 'error'
  data?: User
  error?: string
}

// 런타임 오류 가능성
function render(state: FetchState) {
  if (state.status === 'success') {
    return state.data!.name  // ! 단언 필요 — 위험
  }
}

해결: 판별 유니온 (Discriminated Union)

공통 리터럴 필드(판별자)로 각 상태를 독립 타입으로 정의합니다.

CODE
type FetchState<T> =
  | { status: 'idle' }
  | { status: 'loading' }
  | { status: 'success'; data: T }
  | { status: 'error';   error: string }

이제 narrowing이 자동으로 작동합니다.

CODE
function render(state: FetchState<User>) {
  switch (state.status) {
    case 'idle':
      return <p>시작하려면 버튼을 클릭하세요</p>
    case 'loading':
      return <Spinner />
    case 'success':
      return <p>{state.data.name}</p>  // ✅ data 타입이 User로 좁혀짐, ! 불필요
    case 'error':
      return <p>{state.error}</p>      // ✅ error는 string
  }
}

Exhaustive Check — 빠진 케이스 컴파일 오류로 잡기

새 상태를 추가했을 때 처리 누락을 컴파일러가 알려주게 만들 수 있습니다.

CODE
function assertNever(x: never): never {
  throw new Error('Unhandled case: ' + JSON.stringify(x))
}

type Shape =
  | { kind: 'circle';    radius: number }
  | { kind: 'rectangle'; width: number; height: number }
  | { kind: 'triangle';  base: number; height: number }

function area(shape: Shape): number {
  switch (shape.kind) {
    case 'circle':    return Math.PI * shape.radius ** 2
    case 'rectangle': return shape.width * shape.height
    case 'triangle':  return (shape.base * shape.height) / 2
    default:          return assertNever(shape)  // 모든 케이스 처리 안 하면 컴파일 오류
  }
}

나중에 { kind: 'pentagon'; ... }를 추가하면 assertNever에서 컴파일 오류가 발생해 처리 누락을 즉시 알 수 있습니다.

React에서 활용 — useReducer

CODE
type Action =
  | { type: 'FETCH_START' }
  | { type: 'FETCH_SUCCESS'; payload: User[] }
  | { type: 'FETCH_ERROR';   error: string }

function reducer(state: FetchState<User[]>, action: Action): FetchState<User[]> {
  switch (action.type) {
    case 'FETCH_START':
      return { status: 'loading' }
    case 'FETCH_SUCCESS':
      return { status: 'success', data: action.payload }
    case 'FETCH_ERROR':
      return { status: 'error', error: action.error }
    default:
      return assertNever(action)  // 새 Action 추가 시 누락 감지
  }
}

API 응답 타입 모델링

CODE
type ApiResponse<T> =
  | { ok: true;  data: T }
  | { ok: false; error: string; code: number }

async function fetchUser(id: number): Promise<ApiResponse<User>> {
  const res = await fetch(`/api/users/${id}`)
  if (!res.ok) return { ok: false, error: '요청 실패', code: res.status }
  return { ok: true, data: await res.json() }
}

const result = await fetchUser(1)
if (result.ok) {
  console.log(result.data.name)  // ✅ narrowed to success
} else {
  console.error(result.error)    // ✅ narrowed to error
}

판별 유니온은 Redux 액션, API 응답, 폼 상태 등 "여러 상태 중 하나" 패턴에 모두 적용할 수 있습니다.

공유
S

SHIN

.NET 개발자입니다

GitHub
TypeScript 팁 #2 — 유틸리티 타입 완전 정복

이전 포스트

TypeScript 팁 #2 — 유틸리티 타입 완전 정복

다음 포스트

TypeScript 팁 #4 — 제네릭 제약 조건과 infer 활용

TypeScript 팁 #4 — 제네릭 제약 조건과 infer 활용

같은 카테고리 포스트

TypeScript 팁 #10 — 타입 성능 최적화와 실무 패턴

TypeScript 팁 #10 — 타입 성능 최적화와 실무 패턴

2025년 5월 3일· 3분
TypeScript 팁 #9 — 타입 안전한 에러 처리 패턴

TypeScript 팁 #9 — 타입 안전한 에러 처리 패턴

2025년 5월 1일· 2분
TypeScript 팁 #8 — 선언 병합과 모듈 보강으로 타입 확장하기

TypeScript 팁 #8 — 선언 병합과 모듈 보강으로 타입 확장하기

2025년 4월 29일· 2분

댓글