SHIN
TypeScript 실전 팁 10선
10편// ❌ 어떤 필드가 언제 존재하는지 불명확
interface FetchState {
status: 'idle' | 'loading' | 'success' | 'error'
data?: User
error?: string
}
// 런타임 오류 가능성
function render(state: FetchState) {
if (state.status === 'success') {
return state.data!.name // ! 단언 필요 — 위험
}
}공통 리터럴 필드(판별자)로 각 상태를 독립 타입으로 정의합니다.
type FetchState<T> =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: T }
| { status: 'error'; error: string }이제 narrowing이 자동으로 작동합니다.
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
}
}새 상태를 추가했을 때 처리 누락을 컴파일러가 알려주게 만들 수 있습니다.
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에서 컴파일 오류가 발생해 처리 누락을 즉시 알 수 있습니다.
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 추가 시 누락 감지
}
}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 응답, 폼 상태 등 "여러 상태 중 하나" 패턴에 모두 적용할 수 있습니다.