avatar
Published on

내가 타입스크립트에서 Enum을 잘 쓰지 않는 이유

Author
  • avatar
    Name
    yceffort

Table of Contents

Introduction

이전 글에서도 언급했던 것 처럼, enum은 트리쉐이킹이 되지 않기 때문에 (정확히는 번들러가 무엇을 트리쉐이킹 해야할지 알 수 없으므로) 잘 사용하지 않는 다고 언급했었다.

enum Direction {
  Up,
  Down,
  Left,
  Right,
}

Direction.Up // 0
Direction.Down // 1
Direction.Left // 2
Direction.Right // 3

는 자바스크립트로 아래와 같이 컴파일된다.

'use strict'
var Direction
;(function (Direction) {
  Direction[(Direction['Up'] = 0)] = 'Up'
  Direction[(Direction['Down'] = 1)] = 'Down'
  Direction[(Direction['Left'] = 2)] = 'Left'
  Direction[(Direction['Right'] = 3)] = 'Right'
})(Direction || (Direction = {}))
Direction.Up // 0
Direction.Down // 1
Direction.Left // 2
Direction.Right // 3
// { 0: "Up", 1: "Down", 2: "Left", 3: "Right", Up: 0, Down: 1, Left: 2, Right: 3 }

typescript playground

const enum을 사용하여 위와 같은 큰 트랜스파일을 없앨 수도 있지만, --isolatedModules옵션으로 인하여 별도의 처리가 필요하다고 언급했었다. 만약 그 문제를 넘어간다 하더라도 enum은 문제가 없는 것일까?

--isloatedModules These limitations can cause runtime problems with some TypeScript features like const enums and namespaces. Setting the isolatedModules flag tells TypeScript to warn you if you write certain code that can’t be correctly interpreted by a single-file transpilation process.

숫자형 enum은 예기치 못한 문제를 이르킬 수 있다.

enum Direction {
  Up,
  Down,
  Left,
  Right,
}

declare function move(direction: Direction): void

move(100) // ??

Up, Down, Left, Right가 0, 1, 2, 3 으로 할당되어서 분명 100은 들어가면 안됐을 텐데, 100도 별다른 문제 없이 들어가는 것을 볼 수 있다. 왜 그럴까?

사실 이는 타입스크립트에서 의도된 동작이다.

Ryan Cavanaugh는 타입스크립트 author 아저씨다. 이 issue로 추측컨데, bitwise연산으로 인한 문제로 보인다.

문자형 enum의 경우

구조적 타이핑 세계에서, enum은 named type으로 불리기도 한다. 즉, 값이 올바르고 호환 가능하다 할지라도, 문자열 enum이 필요한 함수나 객체에 값을 전달할 수 없다는 뜻이다. 아래 예시를 살펴보자.

enum Direction {
  Up = 'Up',
  Down = 'Down',
  Left = 'Left',
  Right = 'Right',
}

declare function move(direction: Direction): void

move('Up') // impossible
move(Direction.Up) // possible

이렇듯 문자형 enum과 숫자형 enum의 동작 방식에 차이가 있고, 또 위험성을 안고 있기 때문에 enum 사용을 꺼리는 편이다.

Enum간의 값 비교도 안됨

enum Direction1 {
  Up = 'Up',
  Down = 'Down',
  Left = 'Left',
  Right = 'Right',
}

enum Direction2 {
  Up = 'Up',
  Down = 'Down',
  Left = 'Left',
  Right = 'Right',
}

// This condition will always return 'false' since the types 'Direction1.Up' and 'Direction2.Up' have no overlap.
if (Direction1.Up === Direction2.Up) {
}

아무리 같은 값이라 할지라도, enum 내에 있으면 타입스크립트는 이 값을 비교할 수 없기 때문에 false가 리턴된다.

Union Types을 대신 써보기

우리에겐 union type이 있다.

type Direction = 'Up' | 'Down' | 'Left' | 'Right'

declare function move(direction: Direction): void

move('Up') // possible

만약 진짜 enum, 즉 숫자형 enum 을 쓰고 싶다면, const, 그리고 as const 와 함께 Values<T>의 헬퍼 타입을 써보는 것도 좋다.

const Direction = {
  Up: 0,
  Down: 1,
  Left: 2,
  Right: 3,
} as const

type Values<T> = T[keyof T]

declare function move(direction: Values<typeof Direction>): void

move(Direction.Up) // Ok!
move(0) // Ok!
move(100) // ㅠ_ㅠ
  • enum의 동작과 다르게 결과물이 어떨지 코드를 통해 명확히 알 수 있음
  • 문자열 enum, 숫자형 enum 등으로 바꾼 다고 해서 (값을 바꾼다고해서) 동작에 차이가 발생하지 않음
  • 타입 안전성 확보
  • enum과 동일한 편의성 제공