TypeScript의 Enum 타입

#Enum #typescript

TS에도 Enum 타입은 있지만, JS convert 문제로 Enum을 사용할 수 없는 상황이 있다. TS에서 Enum 처럼 사용할 수 있는 객체 형태는 이렇다.

export const BULLET_TYPE = {
  TASK: 0,
  NOTE: 1,
  EVENT: 2,
} as const;

export type BulletType = typeof BULLET_TYPE[keyof typeof BULLET_TYPE];

조금 복잡하지만 Enum 대신 이 방식을 쓰면 트리 쉐이킹이 더 잘 되어 번들 사이즈를 줄일 수 있다.

const, as const

위 예시에서 변수 앞에 쓴 const는 변수의 재할당을 막는다.

const BULLET_TYPE = { TASK: 0 };
BULLET_TYPE.TASK = 1; // 가능
BULLET_TYPE.NOTE = 0; // 불가

접미사로 붙은 as const는 리터럴 타입을 만들어 준다. 리터럴 타입이란 코드에 작성하는 리터럴 그 자체가 타입이 된 것을 말한다. 즉 객체에 as const를 사용하면 해당 객체의 형태 자체가 타입이 되어, 객체의 값들이 readonly가 된다. 따라서 객체 내부에 있는 키를 읽을 수는 있지만, 값을 바꿀 수는 없다.

const BULLET_TYPE = { TASK: 0 } as const;
BULLET_TYPE.TASK = 1; // 불가
BULLET_TYPE.NOTE = 0; // 불가

const는 JS 런타임에 변수 자체에 다른걸 대입하지 못하게 하고, as const는 TS 컴파일러에게 객체의 값을 타입으로 고정시킨다. 따라서 둘을 함께 사용해야 런타임과 컴파일 타임 모두에서 완벽한 상수를 만들 수 있다.

typeof

객체를 enum처럼 사용하기 위해 type을 만들어서 같이 export 해주는데, 이를 위해 typeof 문법을 사용한다. typeof BULLET_TYPE은 { readonly TASK: 0, readonly NOTE: 1, readonly EVENT: 2 } 라는 설계도를 만든다. (JS처럼 object라는 타입을 리턴하지 않는다.) keyof은 설계도의 key만 뽑아내어, TASK | NOTE | EVENT로 유니온 타입을 만든다.

export type BulletType = typeof BULLET_TYPE[keyof typeof BULLET_TYPE];
// 1. export type BulletType = 설계도[keyof 설계도]; 
// 2. export type BulletType = 설계도[TASK | NOTE | EVENT]; 
// 3. export type BulletType = 0 | 1 | 2; 

이러한 순서로 BulletType은 0 | 1 | 2 라는 리터럴 유니온 타입이 된다.