enum은 자바스크립트에서도 사용할 수 있다
TypeScript에는 흔히 쓰는 string, number, boolean 같은 기본 타입 외에도 조금 특별한 타입이 존재한다. 그 중 하나가 바로 enum(열거형)이다.
enum이란?
enum은 열거형이라고 불리며, 관련 있는 여러 개의 상수 값을 하나의 그룹으로 묶기 위한 타입이다. 예를 들어, 난이도 단계를 표현하고 싶을 때 아래와 같이 선언할 수 있다
enum Level {
NOVICE,
INTERMEDIATE,
ADVANCED,
MASTER,
}
이 코드에서 Level은 열거형 이름이고, NOVICE부터 MASTER까지는 멤버(member) 라고 한다. 기본적으로 가장 첫 번째 멤버는 0부터 시작하여 순차적으로 증가하는 숫자가 자동 할당된다.
자바스크립트로 변환되는 방식
TypeScript의 enum은 자바스크립트로 컴파일될 때 제거되지 않고 아래와 같은 형태의 객체로 남는다:
var Level;
(function (Level) {
Level[Level["NOVICE"] = 0] = "NOVICE";
Level[Level["INTERMEDIATE"] = 1] = "INTERMEDIATE";
Level[Level["ADVANCED"] = 2] = "ADVANCED";
Level[Level["MASTER"] = 3] = "MASTER";
})(Level || (Level = {}));
이 코드는 다음과 같은 객체 구조를 생성한다:
var Level = {
0: 'NOVICE',
1: 'INTERMEDIATE',
2: 'ADVANCED',
3: 'MASTER',
NOVICE: 0,
INTERMEDIATE: 1,
ADVANCED: 2,
MASTER: 3,
};
즉, Level.NOVICE는 0, Level[0]은 'NOVICE'로, 양방향 매핑이 가능하다.
값 지정 및 자동 증가 규칙
enum 멤버는 특정 값으로 명시적으로 지정할 수 있다
enum Level {
NOVICE = 3,
INTERMEDIATE, // 4
ADVANCED = 7,
MASTER, // 8
}
숫자를 지정하지 않으면 이전 멤버의 값에 1을 더한 값이 자동으로 할당된다.
문자열 멤버
문자열도 할당할 수 있다. 하지만 하나라도 문자열을 사용하면 모든 멤버에 초기값을 명시적으로 지정해야 한다
enum Level {
NOVICE = "BEGINNER",
INTERMEDIATE = "MID",
ADVANCED = "EXPERT",
MASTER = "LEGEND",
}
enum Level {
NOVICE,
INTERMEDIATE = "MID",
ADVANCED = "EXPERT",
MASTER, // ❌ 초기화 누락으로 컴파일 에러 발생
}
enum의 값 활용
enum은 값으로도 활용할 수 있으며, 양방향 매핑을 통해 이름을 추출할 수 있다
enum Level {
NOVICE,
INTERMEDIATE,
ADVANCED,
MASTER,
}
const a = Level.NOVICE; // 0
const b = Level[Level.NOVICE]; // "NOVICE"
타입으로서의 enum
enum은 타입으로도 사용할 수 있다. 예를 들어 다음과 같은 함수에서 유용하게 쓰인다
function whatsYourLevel(level: Level) {
console.log(Level[level]);
}
const myLevel = Level.ADVANCED;
whatsYourLevel(myLevel); // ADVANCED
이처럼 enum을 타입으로 사용하면 Level.NOVICE | Level.INTERMEDIATE | ... 와 유사한 유니언 타입 역할을 한다.
숫자 enum vs 문자열 enum
enum Role {
USER,
GUEST,
ADMIN,
}
enum Role2 {
USER = "USER",
GUEST = "GUEST",
ADMIN = "ADMIN",
}
숫자 enum의 경우 다음처럼 동작한다
function changeUserRole(role: Role) {}
changeUserRole(2); // 가능 (Role.ADMIN)
changeUserRole(4); // ❌ 컴파일 에러 (TypeScript 5.0부터 도입된 stricter check)
문자열 enum은 문자열 리터럴 이외의 값은 허용되지 않는다
function changeUserRole2(role: Role2) {}
changeUserRole2(Role2.USER); // 가능
changeUserRole2("USER1"); // ❌ 에러
enum을 브랜드 타입으로 사용하기
enum은 브랜드 속성으로도 사용할 수 있다. 다음은 돈의 종류를 구분하는 예시이다
enum Money {
WON,
DOLLAR,
}
interface Won {
type: Money.WON;
}
interface Dollar {
type: Money.DOLLAR;
}
function moneyOrDollar(param: Won | Dollar) {
if (param.type === Money.WON) {
// Won 타입
} else {
// Dollar 타입
}
}
다만 enum 간의 값이 겹칠 수 있다는 점에 주의해야 한다
enum Money { WON } // WON = 0
enum Water { LITER } // LITER = 0
interface M { type: Money.WON }
interface N { type: Water.LITER }
function moneyOrLiter(param: M | N) {
if (param.type === Money.WON) {
// 항상 여기에 들어옴
} else {
// 이 블록은 실행되지 않음
}
}
Money.WON과 Water.LITER는 모두 0이기 때문에 런타임에서는 구분이 되지 않는다. 같은 enum 내에서만 비교해야 안전하다.
const enum: 최적화된 enum
TypeScript에서는 const enum을 사용해 enum 객체 자체가 생성되지 않도록 할 수 있다
const enum Currency {
WON,
DOLLAR,
}
const won = Currency.WON; // 0
이 경우 컴파일된 자바스크립트는 다음과 같이 치환된다
var won = 0;
즉, 실제 Currency 객체는 존재하지 않기 때문에 다음은 사용할 수 없다
Currency[Currency.WON]; // ❌ 에러 발생
항목 | 설명 |
기본 enum | 숫자 자동 할당 (0부터 시작), 양방향 매핑 가능 |
문자열 enum | 수동 할당 필요, 단방향 매핑 |
타입으로 사용 | enum을 타입으로 지정 가능 (멤버 유니언 역할) |
브랜드 타입 활용 | 멤버 값으로 인터페이스 구분 가능 |
const enum | 성능 최적화를 위한 방식, 객체 생성 없음 |
주의점 | 서로 다른 enum의 값은 런타임에서 구분되지 않을 수 있음 |
TypeScript의 enum은 자바스크립트 코드로도 남기 때문에 런타임에서도 사용할 수 있으며, 다양한 용도로 활용 가능하다. 하지만 내부 동작 방식과 enum 간 비교 시의 주의사항 등을 잘 이해하고 사용하는 것이 중요하다.