Typescript Enum

Seal Park·2022년 6월 26일
1

Typesript Enum

Enum이란?

자바스크립트에서는 원래 열거형(Enumerated Type, 줄여서 Enum)이 없었는데, 타입스크립트에서 Enum을 제공하면서 사용 가능하게 되었습니다. 위키피디아에서는 enum을 아래와 같이 설명합니다.

멤버라 불리는 명명된 값의 집합을 이루는 자료형이다. 열거자 이름들은 일반적으로 해당 언어의 상수 역할을 하는 식별자이다.

대표적인 열거형의 예시로 boolean 타입을 들 수 있는데, boolean 타입은 truefalse로 이루어진 자료형이에요. 이처럼 실제 코딩을 하면서 제한적 이면서 참 또는 거짓이 아닌 보다 더 다양한 상태를 정의하고 싶다면 enum을 사용하여 정의할 수 있습니다.

Enum 사용 방법

타입스크립트에서 enum을 선언하려면, enum이라는 키워드를 통해 선언할 수 있습니다.

enum Direction {
  EAST,
  WEST,
  SOUTH,
  NORTH
}

function setDirection(direction: Direction) { // 선언한 enum은 타입으로 사용이 가능하다.
  // ...
}

Direction이라는 enum을 생성하고, 아래의 setDirection이라는 함수의 인자로 Direction이라는 타입을 설정했어요. 위의 코드처럼, enum은 타입으로써도 사용할 수 있습니다. VSCode에서 함수의 파라미터인 direction으로 자동완성부분을 보면,

number 타입의 method들이 자동완성 목록으로 제공되는걸 볼 수 있어요. 이를 통해 알 수 있는 사실은, enum의 각각 속성의 값들은 숫자를 값으로 가지는 것을 알 수 있습니다.

enum Direction { // #1
  EAST,
  WEST,
  SOUTH,
  NORTH
}

// 실제로 값을 입력하지 않았을 뿐, 타입스크립트는 값을 아래와 같이 자동으로 입력해준다.
enum Direction { // #2
  EAST = 0,
  WEST = 1,
  SOUTH = 2,
  NORTH = 3,
}

enum은 각각 속성들의 값을 지정하지 않으면, 위에서 아래로 0부터 숫자 1씩 늘어나도록 자동으로 설정이 됩니다. 만약 직접 값을 설정하고 싶다면, 위의 코드 예시 중 2번처럼 각각 속성에 값을 입력하면, 값을 설정할 수 있습니다.

enum Direction {
  EAST = 3,
  WEST, // 4
  SOUTH = EAST + WEST, // 7
  NORTH = SOUTH + 5, // 12
}

처음에 enum을 선언할 때 초기 값을 직접 설정할 수 있어요. 값을 설정하지 않으면, 이전 속성에서 1을 더한 값이 되고, 표현식으로도 설정할 수 있습니다.

enum Direction {
  EAST = 'East',
  WEST = 'West',
  SOUTH = 'South',
  NORTH = 'North',
}

또한 속성의 값으로 문자열을 설정할 수 있습니다.

위의 예시 코드들을 통해 알 수 있는 사실은, enum을 통해 생성한 속성 들의 값으로 숫자 또는 문자열을 설정 할 수 있습니다. 정리하자면, enum으로 생성한 속성들은 문자열 또는 숫자로만 설정이 가능합니다. 물론 문자열과 숫자를 섞어서 사용할 수도 있습니다.

역 매핑(Reverse Mapping)

선언한 enum의 각각 속성의 값이 숫자인 경우, 역 매핑을 지원합니다. 예제 코드를 먼저 보겠습니다.

enum Direction {
  EAST,
  WEST,
  SOUTH,
  NORTH,
}

const East = Direction.EAST;
const valueOfEast = Direction[East]; // EAST

가장 마지막 줄을 보면, valueOfEast의 값은 ‘EAST’입니다. 왜 이런 결과가 발생했는지를 알기 위해선, 타입스크립트가 어떻게 컴파일 하는지(트랜스 파일링)를 확인해봐야 합니다.

var Direction;
(function (Direction) {
    Direction[Direction["EAST"] = 0] = "EAST";
    Direction[Direction["WEST"] = 1] = "WEST";
    Direction[Direction["SOUTH"] = 2] = "SOUTH";
    Direction[Direction["NORTH"] = 3] = "NORTH";
})(Direction || (Direction = {}));
const East = Direction.EAST;
const valueOfEast = Direction[East];

컴파일된 코드를 보면, 정방향과 역방향 모두 저장되어있는 것을 볼 수 있습니다. 조금 더 정리해서 코드를 나열해보면,

var Direction = {
  EAST: 1,
  WEST: 2,
  SOUTH: 3,
  NORTH: 4,
  '1': 'EAST',
  '2': 'WEST',
  '3': 'SOUTH',
  '4': 'NORTH',
}

위와 같이 접근이 가능하도록 타입스크립트에서 컴파일 합니다. 그러므로 역 매핑의 첫 번째 코드 예시와 같이 값에 접근할 수 있게 된 이유에요.

const enum

const enum은 기존의 enum보다 더 가성비있고 엄격한 키워드로, 선언 방법과 컴파일 시에 어떤 결과를 내는지 바로 코드 예제를 통해 알아보겠습니다.

// #1 코드 작성 시점
const enum Direction {
  EAST,
  WEST,
  SOUTH,
  NORTH,
}

const East = Direction.EAST;
const West = Direction.WEST

// #2 컴파일 시점
const East = 0 /* Direction.EAST */;
const West = 1 /* Direction.WEST */;

const enumenum과 다르게, 선언한 enum이 컴파일된 후에는 사라진 것을 알 수 있습니다. 더 엄격한 이유는 역매핑 또한 되지 않기 때문입니다. 컴파일 하는 순간 선언한 부분에만 인라인 되고, enum을 선언한 부분은 컴파일 후 삭제가 됩니다.

const assertion

const assertion(상수 단언)은 타입의 범위를 좁혀줍니다. 지금까지 사용했던 Direction 예제를 통해 상수 단언 이전에 비교를 먼저 해보면,

// #1 선언부
const Direction = {
  EAST: 0,
  WEST: 1,
  SOUTH: 2,
  NORTH: 3,
};

// #2 타입스크립트의 타입 추론
const Direction: {
  EAST: number;
  WEST: number;
  SOUTH: number;
  NORTH: number;
}

Direction 속성의 값들을 숫자로 입력 했을 때, 타입스크립트는 각각의 값들의 타입을 number로 추론합니다. 만약 해당 변수 Direction에 상수 단언을 하면,

// #1 선언부
const Direction = {
  EAST: 0,
  WEST: 1,
  SOUTH: 2,
  NORTH: 3,
} as const;

// #2 타입스크립트의 타입 추론
type const = {
  readonly EAST: 0;
  readonly WEST: 1;
  readonly SOUTH: 2;
  readonly NORTH: 3;
}

타입스크립트가 타입 추론을 한 부분을 보면 확연한 차이를 볼 수 있습니다. 몇 가지 차이가 있지만, 대표적으로 각 속성값의 타입이 number가 아닌, 특정 값을 타입으로 추론했습니다. 그리고, readonly 키워드를 보면 알 수 있듯, 각각의 속성을 변경할 수 없도록 추론 합니다.

그렇다면, 무엇을 쓰는게 좋을까?

상황에 따라 다르지만, 각각의 특징을 통해 사용을 고려보는 것이 좋다고 생각해요.

  • enum
    • 타입스크립트가 컴파일 하면, 트리쉐이킹(Tree-Shaking)이 되지 않아, 사용하지 않는 코드도 모두 컴파일이 됩니다. (enum을 컴파일 하면 즉시 실행 함수(IIFE)가 되는데, Rollup과 같은 번들러는 즉시 실행 함수를 ‘사용하지 않는 코드'라고 판단할 수 없기 때문입니다.)
    • 리버스 매핑을 지원하여 유연한 접근이 가능합니다.
  • const enum
    • 타입스크립트가 컴파일 하면, 선언된 부분의 코드는 사라지며, 사용한 코드에 인라인 됩니다.
    • 리버스 매핑을 지원하지 않습니다.
  • const assertion
    • 레코드 타입(Record Type)으로 선언하여 사용한다.
    • 타입스크립트가 컴파일 하면, 일반적인 object 타입의 코드로 컴파일 된다.
    • 타입스크립트는 각각의 속성을 readonly로, 선언한 값을 타입으로 추론한다.

타입스크립트 공식 문서에서는, 어떤 것을 쓸지를 고민하는 사람들에게 아래와 같이 설명했습니다.

In modern TypeScript, you may not need an enum when an object with as constcould suffice

감히 정리해보자면, as const(const assertion)으로 충분히 사용하고 있다면, enum은 필요하지 않을 수 있다라고 설명하는 것 같네요.

마치며

평소에 코딩할 때 enumrecord type을 정해놓은 기준 없이 사용 했었는데, 코드의 일관성을 위해서 어떤 기준을 가지고 사용하는 게 좋을지 고민하다가, 생각보다 다양한 선택지가 있었고, 그에 따른 특징도 있다는 걸 이제야 알게 되었어요.. 위의 자료들을 찾아보면서 반성 하면서, 동시에 많이 배우기도 한 것 같아요.

처음에는 기준을 찾고자 시작했어요. 제가 구글 검색을 잘 못한 탓도 있는지, 대중적으로 널리 사용하는 기준에 대해서는 찾지 못했어요. 조금 허무하지만, 의외의 차이점과 특징을 알게 되어 오히려 더 좋았습니다.

공부 하면서 정리한 자료라 틀린 내용이 있을 수 있어요. 댓글 남겨주시면 감사하겠습니다!

References

https://www.typescriptlang.org/docs/handbook/enums.html#handbook-content

https://hyunseob.github.io/2017/07/18/typescript-enums/

https://medium.com/@seungha_kim_IT/typescript-3-4-const-assertion-b50a749dd53b

https://engineering.linecorp.com/ko/blog/typescript-enum-tree-shaking/

0개의 댓글