Post

상수 사용하기, enum vs keyof typeof

문제

타입스크립트 앱에서 상수를 선언할 때 아래처럼 type과 객체를 따로 선언했다가 바보같은 짓임을 깨달았다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
export type Status = "default" | "active" | "disabled";

export const STATUS = {
  DEFAULT: "default",
  ACTIVE: "active",
  DISABLED: "disabled"
};

const Test = ({
  type = STATUS.DEFAULT
}: {
  type: Status;
}) => {
  return ();
};

enum

enum을 문자열 열거형으로 사용하면 객체로 생성한 것처럼 상수를 선언할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
export enum Status {
  Default = "default",
  Active = "active",
  Disabled = "disabled"
};

const Test = ({
  type = Status.Default
}: {
  type: Status;
}) => {
  return ();
};

하지만 구글링하던 중 enum을 사용하지 않는 게 좋다는 블로그를 보게 된다.

바로 enum을 사용하면 Tree-shaking이 되지 않는다는 것이다.

ts enum 코드를 트랜스파일링하면 IIFE(즉시 실행 함수)를 포함한 js 코드가 생성된다.

그런데 Rollup과 같은 번들러는 IIFE를 ‘사용하지 않는 코드’라고 판단할 수 없어서 Tree-shaking 하지 않는다.

결국 enum 변수를 import하고 실제로는 사용하지 않더라도 최종 번들에는 포함되는 것이다.

1
2
3
4
5
6
7
8
9
10
11
12
// ts
export enum MOBILE_OS {
  IOS = "iOS",
  ANDROID = "Android"
}

// js
export var MOBILE_OS;
(function (MOBILE_OS) {
  MOBILE_OS["IOS"] = "iOS";
  MOBILE_OS["ANDROID"] = "Android";
})(MOBILE_OS || (MOBILE_OS = {}));

또 다른 문제로는 enum을 string과 number 형을 함께 사용하는 이종 열거형으로 정의했을 때 Object.keys, Object.values에 숫자도 포함되는 것이다.

1
2
3
4
5
6
7
8
9
enum NamePolicy {
  phoneNumber = "phoneNumber",
  countryCode = "countryCode",
  tag = "tag",
  ssn = "ssn",
  pollute = 1
}

console.log(Object.keys(NamePolicy)); //  ["1", "phoneNumber", "countryCode", "tag", "ssn", "pollute"]

이는 숫자 enum이 양방향 매핑(bidirectional mapping)을 생성하기 때문이다.

1
2
3
4
5
6
7
8
var NamePolicy;
(function (NamePolicy) {
  NamePolicy["phoneNumber"] = "phoneNumber";
  NamePolicy["countryCode"] = "countryCode";
  NamePolicy["tag"] = "tag";
  NamePolicy["ssn"] = "ssn";
  NamePolicy[(NamePolicy["pollute"] = 1)] = "pollute"; // 양방향 매핑
})(NamePolicy || (NamePolicy = {}));

keyof typeof

enum을 사용하지 않는 다른 방법으로는 keyof typeof가 있다.

객체에 상수를 할당하고, keyof typeof를 사용하여 타입을 정의한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
export const STATUS = {
  DEFAULT: "default",
  ACTIVE: "active",
  DISABLED: "disabled"
} as const;

const Test = ({
  type = STATUS.DEFAULT
}: {
  type: typeof STATUS[keyof typeof STATUS];
}) => {
  return ();
};

위 코드에 나온 as const에 대해 알아보고 가자.

1
2
3
4
5
export const STATUS = {
  DEFAULT: "default",
  ACTIVE: "active",
  DISABLED: "disabled"
};

위와 같이 STATUS 객체를 만들고 마우스를 갖다대면 타입스크립트가 친절히 타입을 추론해준다.

그러나 기대와는 달리 DEFAULT: "default" ...이 아니라 DEFAULT: string ...으로 상수값이 아닌 string으로 추론한다.

const 변수로 객체를 선언했지만 객체 내부의 값들은 바꿀 수 있기 때문이다.

이 때 as const를 사용하면 타입을 리터럴로 바꿀 수 있다.

참고사이트

Enums
TypeScript enum을 사용하지 않는 게 좋은 이유를 Tree-shaking 관점에서 소개합니다.
Typescript에서 효과적으로 상수 관리하기
typescript enum을 쓰면 안된다는 말에 대해

This post is licensed under CC BY 4.0 by the author.