Typescript에서 제공하는 Enum의 형태는 2가지가 존재한다.
숫자기반의 enum 과 문자열기반의 enum
이 2가지 형태의 enum 을 구분할 줄 알아야한다.
각 형태에 따라 런타임 환경에서 javascript 가 값을 읽을 수 있도록 transpile 되는 형태가 다르기 때문이다.
그러면 어떻게 다른지 살펴보자.
1. Enum 의 두가지 기능
TypeScript의 enum은 아래의 2가지 기능을 제공한다.
역방향 매핑(Reverse Mapping)
숫자 기반 enum은 값에서 이름으로, 이름에서 값으로 양방향 매핑을 지원한다.
이를 통해 기존의 key 로 접근하여 value 를 가져올 수 있는 형태에서, 역으로 value 에 접근해 key 를 가져올 수 있는 형태까지 가능해졌다.
실행 시점 접근(Runtime Access)
변환된 코드를 통해 실행 시점에 enum의 값에 접근하고, 이를 통해 동적인 기능을 제공한다.
2. 숫자 기반 enum (열거형 enum)
숫자 기반 enum은 각 멤버에 자동으로 숫자 값을 할당한다.
첫 번째 멤버는 0에서 시작하며, 이후 멤버는 이전 멤버의 값에서 1씩 증가한다.
이와 같은 특징으로 보통 숫자열 기반 enum은 연속적인 값이 필요한 경우 많이 사용된다.
요일이나 월이 적절한 예시로 생각된다.
TypeScript 컴파일러는 이 enum을 JavaScript에서 다음과 같은 형태로 변환한다.
enum DirectionNumber {
UP,
DOWN,
RIGHT,
LEFT,
}
var DirectionNumber;
(function (DirectionNumber) {
DirectionNumber[DirectionNumber["UP"] = 0] = "UP";
DirectionNumber[DirectionNumber["DOWN"] = 1] = "DOWN";
DirectionNumber[DirectionNumber["RIGHT"] = 2] = "RIGHT";
DirectionNumber[DirectionNumber["LEFT"] = 3] = "LEFT";
})(DirectionNumber || (DirectionNumber = {}));
이 코드는 enum의 각 멤버에 숫자 값을 할당하고, 숫자 값으로부터 멤버의 이름을 역으로 찾을 수 있게 해준다.
예를 들어, DirectionNumber.UP은 0이고, DirectionNumber[0]은 "UP"인 것이다.
이게 역방향 매핑이다.
왜 역방향 매핑이 필요한거지? 라는 의문이 있을 수 있다.
타입스크립트는 역방향 매핑을 통해 enum의 유연성을 높이고,
개발자가 enum 값으로부터 멤버의 이름을 쉽게 얻을 수 있게 하려는 의도를 가지고 있다고 하니 이걸로 의문을 해소하자..
3. 문자열 기반 Enum
문자열 기반 enum은 각 멤버에 문자열 값을 명시적으로 할당한다.
TypeScript 컴파일러는 이를 JavaScript에서 아래와 같이 변환한다.
enum DirectionString {
UP = "up",
DOWN = "down",
RIGHT = "right",
LEFT = "left",
}
var DirectionString;
(function (DirectionString) {
DirectionString["UP"] = "up";
DirectionString["DOWN"] = "down";
DirectionString["RIGHT"] = "right";
DirectionString["LEFT"] = "left";
})(DirectionString || (DirectionString = {}));
숫자열 기반 enum 과는 다르게 문자열 기반 enum에서는 역방향 매핑 이 없다!
왜냐? 분명 타입스크립트는 enum의 유연성을 높이고, enum 값으로부터 멤버의 이름을 쉽게 얻을 수 있도록 역방향 매핑을 추가한다는 의도가 있다고 하지 않았었나? 싶지만
문자열 값이 고유해야 하고, 숫자와 같이 연속적이거나 자동으로 생성될 수 없기 때문에
문자열 기반 enum에서는 역방향 매핑이 일어나지 않는다고 한다..
즉, 문자열 기반의 enum 은 단방향 매핑만 생성하여
DirectionString["UP"]은 "up"을 반환하지만, "up"에서 "UP"을 찾는 것은 지원되지 않는다.
4. 숫자+문자열 기반 enum
enum Direction {
MIDDLE, // 0으로 할당
UP = "up",
DOWN = "down",
RIGHT = "right",
LEFT = "left"
}
var Direction;
(function (Direction) {
Direction[Direction["MIDDLE"] = 0] = "MIDDLE";
Direction["UP"] = "up";
Direction["DOWN"] = "down";
Direction["RIGHT"] = "right";
Direction["LEFT"] = "left";
})(Direction || (Direction = {}));
위 코드와 같이 결합된 형태로 transpile 된다고 한다.
다만 주의해야할 점은
숫자와 문자열 값을 혼용하는 경우, 문자열 값을 할당받은 이후의 멤버들은 자동으로 숫자 값을 할당받지 못한다는 점이다. 예시를 보면 아래와 같다.
enum Direction {
MIDDLE, // 0으로 할당
UP = "up",
DOWN = "down",
RIGHT = "right",
LEFT = "left"
CENTER,
}
위의 enum 을 선언 예시를 보면, 문자열 값을 가지고 있는 LEFT key 다음에 숫자열 기반의 CENTER 가 불쑥 선언되어있는 것을 볼 수 있다.
타입 체커는 해당 CENTER 를 보고 바로 ERROR를 내뿜는다.
문자열 기반 enum 멤버 다음에는 명시적인 문자열 기반 enum 이 와야하기 때문이다.
5. const enum
const enum은 TypeScript에서 제공하는 특별한 enum 타입으로,
컴파일 시에 enum 접근이 그 값으로 직접 대체되어 결과 코드에 enum 구조가 남지 않게 된다.
이는 컴파일된 코드의 크기를 줄이고 성능을 최적화하는 데 도움이 된다.
const enum은 숫자 기반 enum뿐만 아니라 문자열 기반 enum에도 사용할 수 있다.
const enum을 사용할 때, TypeScript 컴파일러는 enum 멤버를 사용하는 위치에 직접 멤버의 값을 삽입한다.
이렇게 하면 런타임에 enum 객체에 접근할 필요가 없어지므로, 성능이 개선된다고 한다.
하지만 이로 인해 역방향 매핑과 같은 enum의 일부 기능은 사용할 수 없게 된다.
그냥 런타임 시점에 하나의 "값" 으로 평가되기 때문이다.
헌데 주의점이 하나 있다..
요게 꽤 골치아픈데 enum 멤버에 동적으로 접근하는 코드가 있다면 에러가 터져버린다는 것이다.
바로 예시를 보자..
const enum Directions {
Up = "UP",
Down = "DOWN",
Left = "LEFT",
Right = "RIGHT"
}
const direction = "Up";
console.log(Directions[direction]); // error!!
const enum은 위에서 말했듯 성능을 위하 컴파일 시에 제거되므로,
Directions[direction]과 같은 동적 접근은 유효하지 JavaScript 코드라고 판단된다.
런타임 시점에 Direction 은 사라져 버리기 때문이다.
즉, enum 자체가 그냥 값으로만 사용되는 경우와
자체 인덱스 접근을 통해 하나의 "값"으로만 사용되는 경우에만
const enum 을 활용해주어야 한다.
실제로 프로젝트 중에 이거를 모르고 그냥 쓰다가 왜 그런지도 모르고 const enum 을 안쓴 경우가 있었다.
솔직히 해당 내용에 대한 이해도가 프로젝트 멤버간에 공유되지 않았다면 코드가 꼬여버릴 확률이 있기 때문에 미리 정보 공유를 하거나, 아예 안쓰는 것도 좋은 선택이라고 생각된다.
그냥 const enum은 enum 이 과도하게 많은 경우 성능향상을 위해 선택가능한 하나의 방법 정도로 알아두자.
'책장 > Typescript' 카테고리의 다른 글
readonly any[] vs any[] (0) | 2024.02.24 |
---|---|
잉여 속성 체크 vs 할당 가능 검사 (0) | 2024.02.04 |