TypeScript Enum의 확장성 문제와 대안 탐색

Taesoo Kim·2025년 5월 21일
2
post-thumbnail

최근 프로젝트에서 흥미로운 문제 상황을 마주했습니다. 백엔드 API가 업데이트되면서 기존에 사용하던 상태값이 변경되었고, 이에 맞춰 프론트엔드 코드도 수정해야 했습니다.

// 기존에 사용하던 TaskStatus enum
enum TaskStatus {
  Todo = "TODO",
  InProgress = "IN_PROGRESS",
  Done = "DONE"  // 이 값을 "FINISHED"로 변경해야 함
}

문제는 이 TaskStatus enum이 이미 수십 개의 컴포넌트에서 사용되고 있다는 점이었습니다. Done의 값을 "FINISHED"로 변경하면 기존 코드가 모두 영향을 받게 되고, 특히 런타임에 "DONE"을 사용하는 부분들(예: API 응답 처리, 상태 비교 등)에서 버그가 발생할 위험이 컸습니다.

새로운 enum을 만드는 것도 고려해봤지만, 그러면 동일한 개념을 나타내는 타입이 두 개가 되어버려 코드 관리가 더 복잡해질 것 같았습니다. 이런 고민 끝에 enum에 대해 더 자세히 알아보게 되었고, 몇 가지 흥미로운 대안을 발견하게 되었습니다.

발견한 문제점들

enum을 수정하려고 하면서 다음과 같은 문제점들을 발견했습니다:

  1. 변경의 어려움: enum의 값이 바뀌면(예: Done = "DONE"Done = "FINISHED") 기존에 "DONE"을 사용하던 모든 코드와 데이터가 영향을 받습니다. 이미 배포된 API 응답, DB 데이터, 프론트엔드 비교 로직 등에서 예상치 못한 버그가 발생할 수 있습니다.
// 기존 코드
if (status === TaskStatus.Done) {
  // status가 "DONE"일 때 처리
}

// 값이 "FINISHED"로 바뀌면 기존 비교가 모두 깨짐
  1. 변경의 영향 범위: enum을 수정하면 이를 사용하는 모든 파일에 영향을 미칩니다. 특히 여러 팀이 협업하는 대규모 프로젝트에서는 이런 변경이 예상치 못한 문제를 일으킬 수 있습니다.

  2. 런타임 코드 생성: enum은 타입뿐만 아니라 실제 자바스크립트 객체를 생성합니다. 이는 번들 크기를 증가시키는 원인이 됩니다.

  3. 트리쉐이킹 제한: 번들 크기 문제를 해결하려고 트리쉐이킹을 적용해보았지만, enum은 효과적으로 트리쉐이킹되지 않았습니다.

발견한 해결책: const assertion

이러한 문제들을 해결하기 위한 대안으로 const assertion을 사용해보았습니다:

// 1. 기존 상태 정의를 객체로 관리
const TaskStatus = {
  Todo: "TODO",
  InProgress: "IN_PROGRESS",
  Done: "DONE" // 기존 값 유지
} as const;

type TaskStatus = typeof TaskStatus[keyof typeof TaskStatus];

// 2. 값 변경이 필요할 때, 새로운 객체로 관리
const UpdatedTaskStatus = {
  ...TaskStatus,
  Done: "FINISHED" // 변경된 값
} as const;

type UpdatedTaskStatus = typeof UpdatedTaskStatus[keyof typeof UpdatedTaskStatus];

// 3. 마이그레이션 구간에서 두 값을 모두 지원하는 비교 함수 작성
function isDone(status: string): boolean {
  return status === TaskStatus.Done || status === UpdatedTaskStatus.Done;
}

// 실제 사용 예시
console.log(isDone("DONE"));      // true (기존 데이터)
console.log(isDone("FINISHED"));  // true (신규 데이터)

이 방식은 다음과 같은 장점을 제공합니다:

  1. 유연한 값 변경 및 확장: 기존 값을 유지하면서 새로운 값을 병행 지원할 수 있어, 점진적 마이그레이션이 가능합니다.
  2. 타입 안전성: as const를 통해 각 값이 정확한 리터럴 타입으로 추론됨
  3. 번들 최적화: 일반 객체이므로 트리쉐이킹이 잘 동작하며, 사용하지 않는 값은 번들에서 자동으로 제거됨

결론: 상황에 맞는 선택

이러한 경험을 통해 깨달은 점은, enum이 무조건 나쁘다기보다는 사용 상황에 따라 선택해야 한다는 것입니다:

  • enum 사용이 좋은 경우:

    • 변경될 가능성이 거의 없는 상수 집합
    • 런타임에 실제 객체가 필요한 경우
    • 외부 라이브러리와의 호환성이 필요한 경우
  • const assertion 사용이 좋은 경우:

    • 값 변경이나 확장, 마이그레이션이 예상되는 경우
    • 번들 크기 최적화가 중요한 경우
    • 유연한 타입 시스템이 필요한 경우

참고 자료

profile
뭔 생각을 해. 그냥 하는 거지 뭐

0개의 댓글