최근 프로젝트에서 흥미로운 문제 상황을 마주했습니다. 백엔드 API가 업데이트되면서 기존에 사용하던 상태값이 변경되었고, 이에 맞춰 프론트엔드 코드도 수정해야 했습니다.
// 기존에 사용하던 TaskStatus enum
enum TaskStatus {
Todo = "TODO",
InProgress = "IN_PROGRESS",
Done = "DONE" // 이 값을 "FINISHED"로 변경해야 함
}
문제는 이 TaskStatus
enum이 이미 수십 개의 컴포넌트에서 사용되고 있다는 점이었습니다. Done
의 값을 "FINISHED"
로 변경하면 기존 코드가 모두 영향을 받게 되고, 특히 런타임에 "DONE"
을 사용하는 부분들(예: API 응답 처리, 상태 비교 등)에서 버그가 발생할 위험이 컸습니다.
새로운 enum을 만드는 것도 고려해봤지만, 그러면 동일한 개념을 나타내는 타입이 두 개가 되어버려 코드 관리가 더 복잡해질 것 같았습니다. 이런 고민 끝에 enum에 대해 더 자세히 알아보게 되었고, 몇 가지 흥미로운 대안을 발견하게 되었습니다.
enum을 수정하려고 하면서 다음과 같은 문제점들을 발견했습니다:
Done = "DONE"
→ Done = "FINISHED"
) 기존에 "DONE"을 사용하던 모든 코드와 데이터가 영향을 받습니다. 이미 배포된 API 응답, DB 데이터, 프론트엔드 비교 로직 등에서 예상치 못한 버그가 발생할 수 있습니다.// 기존 코드
if (status === TaskStatus.Done) {
// status가 "DONE"일 때 처리
}
// 값이 "FINISHED"로 바뀌면 기존 비교가 모두 깨짐
변경의 영향 범위: enum을 수정하면 이를 사용하는 모든 파일에 영향을 미칩니다. 특히 여러 팀이 협업하는 대규모 프로젝트에서는 이런 변경이 예상치 못한 문제를 일으킬 수 있습니다.
런타임 코드 생성: enum은 타입뿐만 아니라 실제 자바스크립트 객체를 생성합니다. 이는 번들 크기를 증가시키는 원인이 됩니다.
트리쉐이킹 제한: 번들 크기 문제를 해결하려고 트리쉐이킹을 적용해보았지만, enum은 효과적으로 트리쉐이킹되지 않았습니다.
이러한 문제들을 해결하기 위한 대안으로 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 (신규 데이터)
이 방식은 다음과 같은 장점을 제공합니다:
as const
를 통해 각 값이 정확한 리터럴 타입으로 추론됨이러한 경험을 통해 깨달은 점은, enum이 무조건 나쁘다기보다는 사용 상황에 따라 선택해야 한다는 것입니다:
enum 사용이 좋은 경우:
const assertion 사용이 좋은 경우: