TypeScript로 안전한 상태 관리 패턴

타입 시스템을 상태 모델의 계약으로 삼고 런타임 검증을 결합하면 예측 가능하고 디버그 가능한 애플리케이션 상태를 구축할 수 있습니다.

1. Discriminated Union

공통 태그 필드를 기준으로 상태/액션을 구분하면 컴파일 타임에 누락 케이스를 탐지하고 안전한 exhaustive 체크를 수행할 수 있습니다.


// state.ts
type Idle = { tag: "Idle" };
type Loading = { tag: "Loading" };
type Success = { tag: "Success"; data: User[] };
type Failure = { tag: "Failure"; error: string };

export type AsyncState = Idle | Loading | Success | Failure;

export function fold(s: AsyncState) {
  switch (s.tag) {
    case "Idle": return "...";
    case "Loading": return "Loading...";
    case "Success": return s.data.length + " users";
    case "Failure": return "Error: " + s.error;
    default: const _exhaustive: never = s; return _exhaustive;
  }
}
      

2. Reducer 패턴

액션 또한 태그 기반으로 정의해 reducer 내부에서 케이스별 변이를 명확히 표현하고 사이드이펙트는 미들웨어로 격리합니다.


// actions.ts
type Fetch = { type: "Fetch" };
type Resolve = { type: "Resolve"; payload: User[] };
type Reject = { type: "Reject"; payload: string };
export type Action = Fetch | Resolve | Reject;

// reducer.ts
export function reducer(state: AsyncState, action: Action): AsyncState {
  switch (action.type) {
    case "Fetch": return { tag: "Loading" };
    case "Resolve": return { tag: "Success", data: action.payload };
    case "Reject": return { tag: "Failure", error: action.payload };
  }
}
      

3. Zod로 런타임 검증

외부 입력은 스키마 기반으로 파싱해 타입 단언을 제거하고 잘못된 형태의 데이터가 상태 그래프에 유입되는 것을 차단합니다.


// schema.ts
import { z } from "zod";
export const UserSchema = z.object({
  id: z.string(),
  name: z.string(),
});
export const UsersSchema = z.array(UserSchema);

// service.ts
export async function fetchUsers() {
  const res = await fetch("/api/users");
  const json = await res.json();
  return UsersSchema.parse(json);
}
      

4. 실전 팁

5. 마무리

태그드 유니온과 Zod 검증의 결합은 타입 안전성과 실행 안전성을 동시에 충족하며 유지보수성을 체계적으로 향상시킵니다.