typescript에서 enum을 사용하지 말아야 하는 이유

Peter Roh·2022년 11월 28일
0

enum의 사용

enum은 서로 연관된 상수들의 집합으로 여러 장점이 있어서 개발 중 자주 이용된다. enum을 사용함으로써 프로그래머는 허용 가능한 값을 제한하고 실수를 방지할 수 있다. 하지만 typescript에서 enum은 여러 가지 문제가 있다.

Reverse mappings

typescript는 결국 javascript로 transpile된다. 이 과정에서 예상치 못한 동작들이 발생하곤 한다. 예를 들어, 다음의 예시를 보자.

enum LoginMethod {
  facebook,
  twitter,
  email
}

console.log(Object.values(LoginMethod)); // [ 'facebook', 'twitter', 'email', 0, 1, 2 ]

왜 이런 결과가 발생했을까? 이것은 의도된 동작일까? 그 이유는 transpile된 javascript의 코드를 보면 알 수 있다. enum은 javascript에 없기 때문에 결국 javascript의 어떤 문법으로 변하는데, 그 결과는 다음과 같다.

var LoginMethod;
(function (LoginMethod) {
    LoginMethod[LoginMethod["facebook"] = 0] = "facebook";
    LoginMethod[LoginMethod["twitter"] = 1] = "twitter";
    LoginMethod[LoginMethod["email"] = 2] = "email";
})(LoginMethod || (LoginMethod = {}));

javascript에서 해당 enum은 name과 value가 모두 저장되도록 변경되었다. 그렇기 때문에 위에서 Object.values(LoginMethod)를 출력했을 때 그런 결과가 나온 것이다. (한마디로 의도된 동작이다.)

... In this generated code, an enum is compiled into an object that stores both forward (name -> value) and reverse (value -> name) mappings.
https://www.typescriptlang.org/docs/handbook/enums.html#reverse-mappings

또한, 이러한 enum을 이용하여 함수를 작성한 경우를 생각해보자.

function doSomething(loginMethod: LoginMethod): void {
  // 어떤 동작
}

이러한 함수를 작성했을 때 다음과 같은 문제점이 발생한다.

doSomething(12345) // 안에 어떤 숫자를 넣어도 동작함

enum을 이용하여 허용 가능한 값을 제한하는 데 실패했다.

String enum

그렇다면 String enum을 사용하면 어떨까? String enum의 경우, reverse mapping이 발생하지 않기 때문에 다음의 코드도 정상 작동한다.

enum LoginMethod {
  facebook = "Facebook",
  twitter = "Twitter",
  email = "Email"
}

console.log(Object.values(LoginMethod)); // [ 'Facebook', 'Twitter', 'Email' ]

또한, 함수 작성시에도 오류를 정상적으로 잡아준다.

function doSomething(loginMethod: LoginMethod): void {
  // 어떤 동작
}

doSomething(LoginMethod.facebook) // 정상 동작함. 

하지만, 다음의 코드는 동작하지 않는다.

doSomething("Facebook") // error

이런 방식으로 코드를 작성할 수 없는 것이 불편한 경우들이 있기 때문에 String enum의 사용을 피하게 되는 경우들이 있다.

const enum

const enum을 사용하여 다음과 같이 작성하면 어떨까?

const enum LoginMethod {
  facebook,
  twitter,
  email
}

console.log(LoginMethod.email); // 2

const enum을 사용시 javascript 코드를 보면 다른 enum들과 달리 해당 부분이 코드에서 제거된다. 그 대신, 해당 값이 사용된 부분을 대체하여 코드가 작성된다. 그 결과는 다음과 같다.

console.log(2 /* LoginMethod.email */); // LoginMethod.email이 작성된 곳에 2가 대신 들어감

코드가 가벼워지고, reverse mapping이 되는 문제가 없기 때문에 유용한 방법 같지만, typescript 문서에서는 const enum을 사용하지 말라고 말하고 있다.

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

해결책

const LoginMethod = {
  facebook: "Facebook",
  twitter: "Twitter",
  email: "Email"
} as const;

type LoginMethod = typeof LoginMethod[keyof typeof LoginMethod];

이렇게 작성하면 모든 결과가 정상적으로 출력됨을 알 수 있다.

function doSomething(loginMethod: LoginMethod): void {
  // 어떤 동작
}

doSomething(LoginMethod.facebook) // Facebook
doSomething("Email") // Email
doSomething("sms") // error. 잘못된 문자열 입력시 동작하지 않음. 

console.log(Object.values(LoginMethod)); // [ 'Facebook', 'Twitter', 'Email' ]

reverse mapping도 일어나지 않았고, 올바르게 입력 값을 제한해주고 잘못된 입력을 방지해줌으로써 실수도 방지해준 것을 확인할 수 있다.

typescript 4.9

만약 위의 코드에서 LoginMethod의 키가 될 수 있는 값들을 한 곳에서 관리하고 싶다면 typescript 4.9 이상의 버전에서는 satisfies 문법을 이용하여 다음과 같이 쓸 수 있다.

const loginMethods = ['facebook', 'twitter', 'email'] as const;

type LoginMethodKeys = typeof loginMethods[number];

const LoginMethods = {
  facebook: "Facebook",
  twitter: "Twitter",
  email: "Email"
} as const satisfies Record<LoginMethodKeys, string>;

type LoginMethod = typeof LoginMethods[keyof typeof LoginMethods];

function doSomething(loginMethod: LoginMethod): void {
  console.log(loginMethod)
}

doSomething(LoginMethods.twitter); // Twitter
doSomething("Twitter"); // Twitter
doSomething("Twiter"); // error. 잘못된 문자열 입력시 동작하지 않음. 

console.log(Object.values(LoginMethods)) // [ 'Facebook', 'Twitter', 'Email' ]

이렇게 작성하면 모두 정상적으로 동작할 뿐 아니라, 개발 중 loginMethods에 한 가지 방법을 더 추가할 경우 영향을 주는 부분에 에러가 표시됨으로써 실수를 방지해줌을 확인할 수 있다.

더 읽어 볼 문서

tree shaking의 관점에서 typescript의 enum의 문제를 다룬 글은 Line Engineering blog에 잘 소개되어 있다.

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

Reference

https://www.youtube.com/watch?v=0fTdCSH_QEU
https://www.typescriptlang.org/docs/handbook/enums.html
https://engineering.linecorp.com/ko/blog/typescript-enum-tree-shaking/

0개의 댓글