본문 바로가기
언어/TypeScript

TypeScript 와 Enum 문제

by svcbn 2025. 12. 23.

03/26

 

Enum 은 보통, 자주 사용해야 할 관련된 상수들을 정의해서 모아놓을 수 있기 때문에, 편리하게 종종 사용하게 된다. 다시 말해, 어떤 변수가 가질 수 있는 값의 후보군을 미리 정해두고 그 안에서만 선택하게 만들기 때문에, 관리가 편하고 휴먼에러를 줄일 수 있는 방법으로서 유용하다.

그런데 이런 Enum 도, TypeScript 에서 사용할 때는 문제가 생길 수 있다는데...

 

Tree - shaking 불가능

TS 는 enum 을 컴파일할 때 즉시 실행 함수(IIFE) 형태의 JavaScript 코드를 생성한다. 문제는, 이런 코드는 정적 분석이 어려워서 번들러(Webpack, Rollup 등) 가 사용하지 않는 코드를 제거하지 못하고 최종 번들에 포함시킨다는 것.

너무 본론만 설명해서 (두괄식의 장점이자 단점이라고 할 수 있다), 추가 설명이 필요해 보이는 부분들을 마저 설명해보자.

IIFE (Immediately Invoked Function Expression)
즉시 호출 함수 표현식은, 함수를 정의하자마자 곧바로 실행하는 JS 디자인 패턴이다.

(function () {
    console.log("정의되자마자 바로 실행됩니다!");
})();

// 화살표 함수 버전
(() => {
    console.log("이것도 즉시 실행됩니다.");
})();

익명함수라고 알고 사용하고 있던 그것. 이를 사용하는 이유는, 외부 스코프에서 접근할 수 없기에, 캡슐화와 전역 변수 오염 방지가 가능하다. 그렇기 때문에, 단 한 번만 실행되어야 하는, 설정이나 초기화 로직에 적합한 방식.

Tree - shaking
트리쉐이킹은 JS 번들링 과정에서 사용하지 않는 코드를 제거하여 최종 파일 크기를 줄이는 최적화 기술이다. 이를 마치 나무를 흔들어 죽은 잎사귀를 떨어뜨리는 행동에 비유한 것.
ES6 의 정적 모듈 구조(import/export) 에서는, 번들러가 코드를 실행하지 않고도 어떤 함수나 변수가 실제 사용되는지 분석하고, import 로 불러왔지만 코드 내에서 한 번도 참조되지 않은 항목은 최종 번들 파일에서 제외해 트리쉐이킹을 실행한다.

문제가 발생하는 부분은, TS 의 enum 은 컴파일 시 단순한 상수가 아니라 IIFE 를 포함한 복잡한 객체로 변환된다는 것.
컴파일 전의 TS 에 이런 enum 이 있다고 하면,

// TypeScript
enum Direction { Up, Down }

컴파일 하는 경우 JS 로는 이렇게 변환된다.

// JavaScript
var Direction;
(function (Direction) {
    Direction[Direction["Up"] = 0] = "Up";
    Direction[Direction["Down"] = 1] = "Down";
})(Direction || (Direction = {}));

번들러는 위 코드가 Side Effect 를 가질 수 있다고 판단한다. 다시 말해서, 함수 내부에서 외부 변수를 조작할 가능성이 있기 때문에, Direction 을 실제로 사용하지 않더라도, 위험해서 삭제하지 못하는 코드로 분류한다는 것.


Reverse Mapping 의 혼란

역방향 매핑은, TS 의 숫자형 Enum 에서만 발생하는 특징으로, key >> value 를 찾을 수 있을 뿐만 아니라 value >> key 를 찾을 수 있는 기능을 말한다. 하지만, 이 과정에서 코드의 복잡성과 예측 불가능함을 발생시키는데,

단순한 enum 예시를 하나 들어보면,

// TypeScript
enum Status {
  Success = 200,
}

컴파일된 JS 코드를 보면 그 이유를 알 수 있다.

// 컴파일된 JavaScript 결과 (이중 할당)
var Status;
(function (Status) {
    Status[Status["Success"] = 200] = "Success";
})(Status || (Status = {}));

이 코드가 실행되면, 예시로 든 객체는 형태가 이상해지는데...

{
  "Success": 200,  // 정방향 (Key -> Value)
  "200": "Success" // 역방향 (Value -> Key)
}

무려 양쪽을 모두 포함한 이상한(?) 객체가 된다????

이로 발생할 수 있는 문제는,

Key 를 순회하는 경우, enum 의 모든 항목을 반복문으로 돌릴 때, 숫자 값까지 키로 취급되어 출력된다. 이에 더해, 역방향 매핑 구조 때문에 선언되지 않은 임의의 숫자를 할당해도 TS 가 에러를 잡지 못하는 경우도 발생한다.

또한, 문자열을 값으로 사용하는 Enum 은 역방향 매핑을 생성하지 않으므로, 동일한 Enum 이더라도 타입에 따라 객체의 구조가 달라진다.


해결 방안

Const Assertion

as const 를 사용하면, 별도의 런타임 객체 생성 없이 Type 시스템을 활용할 수 있으며, 트리쉐이킹도 완벽하게 지원된다.

const Direction = {
  Up: 0,
  Down: 1,
} as const;

// 타입 추출
type Direction = typeof Direction[keyof typeof Direction];

 

Union Types

또는, 단순한 값의 집합만이 필요하다면 유니온 타입을 사용하는 것이 가장 가볍고 직관적이다.

type Status = 'Pending' | 'Success' | 'Failed';

 

정리하자면, 런타임 성능과 번들 크기 최적화를 위해, 또한 혼란을 방지하기 위해, enum 대신 const assertion 이나 Union Types 를 사용하는 것이 현대 TypeScript 에서 권장하는 관례. 하지만, 현실적으로는 진행중인 프로젝트에서 이미 사용하고 있는 방식이 있다면 그와 Convention 을 맞추는 것이 나을 수 있다.

'언어 > TypeScript' 카테고리의 다른 글

옵셔널 체이닝(?.)  (0) 2025.12.19
declare  (0) 2025.12.02
TypeScript import 오류들  (0) 2025.11.25
Union / Intersection Type  (0) 2025.01.08
extends / implements  (0) 2024.09.23