개발/타입스크립트

[타입스크립트] enum과 enum의 리버스 맵핑에 대하여

핸디(Handy) 2024. 5. 8. 00:17

들어가며

이번에 회사에서 신입 개발자를 뽑게 되면서 드디어 함께 스터디를 할 가까운 사람이 생겼다. 

그래서 서로 얘기를 나눠보다가 둘 다 "우아한 타입스크립트 with 리액트" 책을 가지고 있고, 읽지 않았다는 공통점을 발견하고 이 책으로 시작을 하기로 했다.

각자 내용을 읽고 확인하고, 더 찾아볼 내용이 있으면 공유해보자라고 했다.

2장 66p에서 enum과 유니온 타입에 관해 내용이 있었는데,

그 중에서 "enum의 리버스 매핑 기능은 컴파일러에서 처리되면 안 되는 동작이라고 생각해요" 라는 내용을 보았다.

옳다구나하고 이번엔 enum의 리버스 매핑 기능에 대해 알아보도록 하자.

ENUM

우선 나름 인터넷에서 글을 열심히 찾아 읽는 프론트 개발자들은 이런 얘기를 들어보았을 것이다.

"enum은 타입스크립트의 기능이지만, 자바스크립트로 컴파일될때 사리지지않아 성능에 영향을 줄 수 있어요"

맞다. enum은 IIFE로 변환되어 실제 성능에 영향을 준다.

하지만 객체 하나 추가된다고 성능에 영향을 주는것보다 안쓰는 코드나 css를 줄이는게 성능상에 더 큰 영향을 주지 않을까 반론해본다.

우선 변환을 봐보자

// ts에서는 이렇게 보이나
enum Color {
  Red,
  Green,
  Blue
}

// js에서는 아래처럼 된다
var Color;
(function (Color) {
    Color[Color["Red"] = 0] = "Red";
    Color[Color["Green"] = 1] = "Green";
    Color[Color["Blue"] = 2] = "Blue";
})(Color || (Color = {}));

이렇게 보여서 복잡해보이지만, 이것을 한번 더  풀어쓰면 아래처럼 된다.

var Color = {
  Red: 1,
  Green: 2,
  Blue: 3,
  '1': 'Red',
  '2': 'Green',
  '3': 'Blue',
}

즉 객체의 key-value 쌍이 2배가 된다. 

그리고 값을 보면 리버스맵핑에 대한 느낌적인 느낌이 온다.

red로 1를 가져올수 있듯이 1로 red를 가져올수 있는 뭔가 맵핑되어 있는 느낌이!!

아무튼 이렇게 객체로 변경되어 성능상에 영향을 줄 수 있다. (다시 한번 말하지만 객체 하나 추가된다고 성능상에 큰 이슈를 주지 않는다)

사용법

내가 설명하기보다는 이거 보고 오면 된다.

 

TypeScript enum guide: get started in 5 minutes

Enumerations (or enums) are a supported data type in TypeScript. Enums are used in most object-orient...

dev.to

사용 예시

실은 적절한 예시이 떠오르지 않아 챗형한테 물어봤다. 그중에 괜찮다고 생각한것 토대로 2가지를 살펴보겠다.

enum의 역방향 매핑 자체를 이용

enum OrderStatus {
  PENDING = 1,
  SHIPPED,
  DELIVERED,
}

function getOrderStatus(order: { status: number }) {
  const statusName = OrderStatus[order.status]; // 역방향 매핑을 통해 상태 이름 얻기
  if (statusName) {
    console.log(`Order status: ${statusName}`);
  } else {
    console.log("Unknown order status");
  }
}

const order = { status: 2 };
getOrderStatus(order); // "Order status: SHIPPED"

이런 방식으로 사용한다고 챗형이 알려줬다.

내 상황에선 어떻게 사용할까 싶어 고민을 하다가, 프론트쟁이는 서버에서 오는 값과 실제 보여지는 값을 변환하는 작업을 한다.

예를 들어 서버에서 스네이크 케이스로 온 값을 파스칼로 보여주는 작업이 있다 이를 이용하면 dto변환을 좀더 잘 할수 있지 않을까 생각했는데..

enum Fruits {
  Apple = "APPLE",
  Banana = "BANANA",
  Cherry = "CHERRY",
}

// 정방향 매핑
console.log(Fruits.Apple); // 출력: "APPLE"

// 문자열 기반에서는 역방향 매핑이 제공되지 않음
console.log(Fruits["APPLE"]); // undefined

문자열 기반인 경우 역방향 맵핑이 제공되지 않는다.. 이러면 더욱 필요없는거같은데..

typescript enum reverse mapping use case를 검색해보면 가장 상단에 뜨는 글이다.

그리고 예시 코드로 알려주는 것이 아래와 같다.

enum Direction {
  Up = 'UP',
  Down = 'DOWN',
  Left = 'LEFT',
  Right = 'RIGHT',
}

// Reverse mapping
const directionName: string = Direction.Up; // 'UP'
const directionValue: Direction = Direction['UP']; // Direction.Up

근데 여기에 문제가 있는데 typescript Enum은 방금 말했다시피 문자열에 대한 리버스맵핑을 제공하지 않는다. 즉 구라다.

낚여버렸다.

로그 및 모니터링

enum LogLevel {
  INFO = 0,
  WARN,
  ERROR,
}

function logEvent(level: LogLevel, message: string) {
  const levelName = LogLevel[level]; // 역방향 매핑을 사용하여 레벨 이름 얻기
  console.log(`[${levelName}] ${message}`);
}

logEvent(LogLevel.INFO, "Application started"); // "[INFO] Application started"
logEvent(LogLevel.ERROR, "An error occurred"); // "[ERROR] An error occurred"

이 예시가 유용한 이유는 logEvent에서도 enum를 통해 정적인 logLevel를 줄 수가 있고, log를 찍는 곳에서도 값을 보여줄수 있기 때문이다.

물론 아래처럼도 된다.

type LogLevel = "INFO" | "WARN" | "ERROR";

function logEvent(level: LogLevel, message: string) {
  console.log(`[${level}] ${message}`);
}

logEvent("INFO", "Application started"); // "[INFO] Application started"
logEvent("ERROR", "An error occurred"); // "[ERROR] An error occurred"

이것도 생각해보니 굳이라는 생각이 들면서도 뭔가 LogLevel.INFO가 clean code같다.

다른 레퍼런스

 

 

Nine terrible ways to use TypeScript enums, and one good way.

TypeScript enums get a lot of hate. And not for an unjustified reason: they have so many potential foot-guns.

bluepnume.medium.com

그 다음 글에서는 다른 방식으로 쓰는건 테러한 방법이라고 하고 오직 하나만이 봐줄법하다라는 식으로 말씀하신다.

그 예시는 이렇게 변환할 때밖에 없다고 한다. 

enum Suit {
  Hearts = 'hearts',
  Diamonds = 'diamonds',
  Clubs = 'clubs',
  Spades = 'spades'
}

근데 이 방법도 객체 맵핑으로 가능하다.

enum Suit {
  Hearts = 'hearts',
  Diamonds = 'diamonds',
  Clubs = 'clubs',
  Spades = 'spades'
}

const SuitMap = {
  "Hearts": "hearts",
  "Diamonds": "diamonds",
  "Clubs": "clubs",
  "Spades": "spades"
}

console.log(Suit.Hearts); // "hearts" 
console.log(SuitMap["Hearts"]); // "hearts"

 

마무리

이번글을 통해서 enum에 대해 알아봤다.

결론은 굳이 쓸일 없으면 안써도 된다.