article thumbnail image
Published 2022. 11. 12. 14:36
728x90
타입스크립트 enum 정리

❓상황

타입스크립트 기본 개념 중 하나인 enum 정리

 

📖 typescript 란?

자바스크립트에 타입이 추가된 자바스크립트 확장버전이라고 생각하면 쉽다.

 

기존에 자바스크립트를 사용할때는, 코드 작성시에 타입을 적지 않기때문에 구문에러가 발생해도 코드상에는 문제가 없기때문에 오류를 발견하기 어렵다.

 

만약, 코드 작성할때 타입을 명시한다면 타입스크립트를 자바스크립트로 컴파일하는 단계에서 구문에러가 catch 되는 연유로인해, typescript가 javascript보다 더 사랑받는 존재이지 않나 싶다.

 

🧮 정리

기본 문법

자바스크립트의 객체 형태와 유사하게 key는 문자열, value는 숫자나 문자열을 갖는 형태로 나타낼 수 있다.

enum Direction {
  Up = 'String',
  Down = 1000,
  Left = true,  // error! : value는 숫자와 문자열만 가능
  0 = 1	        // error! : key는 문자열만 가능
}

 

숫자 열거형

value값을 생략하면, 맨 처음 key는 0을 갖게된다.

그리고 두번째 key부터는, 이전 key의 value + 1의 값을 자신의 value로 갖는다.

enum Direction {
  Up,    // 0
  Down,  // 1
  Left,  // 2
  Right  // 3
}

 

중간에 숫자를 넣어, 숫자를 변경할 수도 있다.

enum Direction {
  Up,        // 0
  Down = 3,  // 3
  Left,      // 4
  Right      // 5
}

 

숫자 열거형 enum의 경우에는 상수인 숫자(constraint member)말고, 함수에서 계산된 값으로도(computed member)를 가질 수 있다.

해당 함수에서 계산된 값은, 무조건 숫자를 return하는 함수여야한다. 그렇지 않으면 에러가 발생한다.

function getSomeString () {
    return 'string'
}

function getSomeNum () {
    return 0
}

enum Enum1 {
  A = getSomeString(), // error!
  B,
}

enum Enum2 {
  A = getSomeNum()
  B,
}

 

숫자 열거형 enum을 사용하여, 하드코딩과 실수를 줄일 수 있다는 장점이 있다.

enum Response {
  No,
  Yes,
}

function respond(recipient: string, message: Response): void {
  // if (message === 0) {....}
  // if (message === 1) {....}
}

respond("choco cake", Response.Yes);

 

문자 열거형

value값이 모두 문자로 이루어진 것을 문자 열거형이라고 한다.

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

 

복합 열거형

value값이 문자 또는 숫자로 이루어진 것을 복합 열거형이라고 한다.

그러나, 복합 열거형보단 숫자 또는 문자 단일형태인 열거형을 권장한다.

enum SwitchEnum {
    No = 0,
    Yes = "YES",
}

 

런타임 단계에서의 enum

자바스크립트의 객체처럼 접근할 수 있다.

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

console.log(Direction.Up)

 

⭐ enum을 이용해 타입 생성하기

keyof typeof 를 사용하면 열거형의 key를 문자열로 나타내는 타입을 가져 올 수 있다.

enum DirectionEnum {
    Up = "UP",
    Down = "DOWN",
    Left = "LEFT",
    Right = "RIGHT",
}

// type DirectionType = "Up" | "Down" | "Left" | "Right"
type DirectionType = keyof typeof DirectionEnum;

 

⭐ 컴파일 단계에서의 숫자 열거형 enum : 리버스 매핑

enum은 자바스크립트의 객체 형태로 변환이 되는데, 

특이한 점은, key로 value를 접근할 수 있고, value로 key를 접근할 수 있다는 점이다.

enum Direction {
  Up,    // 0
  Down,  // 1
  Left,  // 2
  Right  // 3
}

console.log(Direction[0])    // 'Up'
console.log(Direction.Up)    // 0

// 컴파일
var Direction;
(function (Direction) {
    Direction[Direction["Up"] = 0] = "Up";
    Direction[Direction["Down"] = 1] = "Down";
    Direction[Direction["Left"] = 2] = "Left";
    Direction[Direction["Right"] = 3] = "Right"; // 3
})(Direction || (Direction = {}));

 

⭐⭐컴파일 단계에서의 enum : const modifier

enum을 사용하는 이유는 자바스크립트의 객체처럼 key-value 형태로 값을 만들 수 있고, 해당 값을 이용하여 type을 만들 수 있다는 점이다.

 

그러나 대체로 enum이 사용되지 않는 가장 큰 이유 중 하나는, 컴파일시 코드가 자바스크립트로 남아있다는 것이다.

type alias나, interface가 컴파일되면 자바스크립트로 변환되지 않지만, enum은 컴파일시 자바스크립트 코드로 남게 된다는 점이다.

 

해당 단점의 연장선상의 개념으로 Tree-shaking가 있는데, 간단하게 말해 선언 되었지만 실제로 사용되지 않는 코드를 삭제해주는 기능이다.

enum은 Tree-shaking이 되지 않는다.

enum Direction {
  Up,    // 0
  Down,  // 1
  Left,  // 2
  Right  // 3
}

// 컴파일
var Direction;
(function (Direction) {
    Direction[Direction["Up"] = 0] = "Up";
    Direction[Direction["Down"] = 1] = "Down";
    Direction[Direction["Left"] = 2] = "Left";
    Direction[Direction["Right"] = 3] = "Right"; // 3
})(Direction || (Direction = {}));

// 보기 쉽게 변환된 컴파일 코드
const Direction = {
  "0": "Up",
  "1": "Down",
  "2": "Left",
  "3": "Right",
  "Up": 0,
  "Down": 1,
  "Left": 2,
  "Right": 3
}

 

해당 단점을 해결 하기 위해, enum을 자바스크립트로 변환되지 않게 하는 방법이 있는데, const 키워드를 enum 키워드 앞에 붙이는 것이다.

const enum Direction {
  Up,    // 0
  Down,  // 1
  Left,  // 2
  Right  // 3
}

// 컴파일이 아무것도 되지 않는다.

 

비록 const enum은 컴파일 되지 않지만, key로 접근하여 value값을 얻어 낸 숫자 값은 컴파일되면 어디에서 발생된 값인지 주석으로 표시해준다.

const enum DirectionEnum {
    Up,
    Down,
    Left,
    Right,
}

console.log(DirectionEnum.Up)

// 컴파일
console.log(0 /* DirectionEnum.Up */);

 

하지만, const enum도 문제점이 존재한다.

babel이 const enum을 트랜스파일링 못해, babel-plugin-const-enum 플러그인을 추가로 사용해야한다.
문자열 값이 유니코드로 생성하기 때문에 빌드 파일의 크기가 커지고, 컴파일된 파일에서 해당 값의 출처를 알 수 없다.
const enum EnumKeyboard {
  "GREETING" = "안녕하세요. 반가워요!!"
}
console.log(EnumKeyboard['GREETING']);

// 컴파일 : 문자열이 유니코드로 나와 출처를 알 수 없다.
console.log("\uC548\uB155\uD558\uC138\uC694. \uBC18\uAC00\uC6CC\uC694!!" /* 'GREETING' */);

 

이러한 문제를 해결하기 위해, as const 가 사용된다.

 

⭐⭐⭐const enum 대체 : as const ( = union type)

우선 enum의 특징인, 한번 선언한 이후에는 바꿀 수 없다는 점이다. 흔히 enum은 상수이다 라고 하기도 한다.

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

DirectionEnum[Up] = 'string'    // error!

 

enum 처럼 상수여서 값을 변경할 수 없게 만드는 방법은 as const를 이용하는 것이다.

예를 들어 배열에 as const를 사용하면 배열 값을 수정할 수 없는 읽기 전용으로 바뀌게 된다.

const arr = [1,2,3,4,5];
arr[0] = 100;    // [100,2,3,4,5]

const arr = [1,2,3,4,5] as const;
arr[0] = 100;    // error!

 

한번 선언된 이후 enum 처럼 상수여서 값을 바꿀 수 없으면서, Tree-shaking 기능이 동작하고, 문자열 value가 유니코드로 생성되지 않고 어디서부터 온 value인지 출처알 수 있는 방법은 javascript 객체에 as const를 붙여 enum처럼 활용하는 것이다.

 

해당 방법을 이용해, enum가 상수를 가지는 것 처럼 object도 상수를 가지게 함으로 해당 문제를 해결 할 수 있다.

타입스크립트의 enum 대신, as const 를 적극 사용하자!

const DirectionObj = {
    Up: 1,
    Down: 2,
    Left: 3,
    Right: 4,
} as const

// type DirectionType1 = "Up" | "Down" | "Left" | "Right"
type DirectionType1 = keyof typeof DirectionObj;

// type DirectionType2 = 1 | 2 | 3 | 4
type DirectionType2 = typeof DirectionObj[keyof typeof DirectionObj];

 

참조

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

https://xpectation.tistory.com/218

https://mugglim.tistory.com/9

복사했습니다!