물론 내가 많은 회사, 그리고 많은 기간 재직해본 것은 아니지만 항상 다음과 같은 상황이 있다.
예를 들어 학생들이 좋아하는 교과목을 서버가 받아야하는 상황이다. 프론트 상황에서는 당연하게도 과학, 수학, 영어 이런식으로 명칭이 보여야는 것이 맞겠지만 서버는 그것이 아닌 것 같았다.
항상 number 타입의 값을 받기를 원하는 notion, sweggar 명세서가 보이기 때문이다.
사실 테이블에서 차순으로 인덱스 값을 매긴 그 값을 편하게 받기를 원해서 그러는 것이라 생각되기에 이해는 간다.
그렇다면 우리는 무엇을 고민해봐야할까?
사실 뭐 값이 진짜 얼마 없으면 다음과 같이 정리해서 보낼 수 있게 작성하면 된다.
switch(subject) :
case "수학" :
return 1;
case "국어" :
return 2;
...
이런 식으로 switch 문으로 사용하는 방법도 있다. 하지만 case 문이 너무 많아지면 이렇게 return 으로 Id 값을 만들어내는 것도 번거로워질 수도 있으며, 추후에 서버로부터 Id을 받고 이에 해당되는 원래 텍스트 값을 가져오는 것도 로직을 개발해야됨으로 따따블로 번거로워질 수도 있다.
그래서 나는 이때 꾀를 부리게 되었다.
타입스크립트에서는 객체타입인 Object와 비스무리한 친구가 있는데, 숫자 혹은 문자열 값에 이름 부여가 가능한 열겨형 타입을 Enum이라고 한다.
서버가 원하는 숫자값을 주기 위해서 다음과 같이 작성해 보았다.
아, 만약 우항을 넣기 싫으면 넣지 않아도 되지만 시작은 index 매기는 것처럼 0부터 시작된다.
export enum SubjectEnum {
과학 = 1,
수학 = 2,
국어 = 3,
영어 = 4,
}
이렇게 해놓으면 console에는 다음과 같이 보인다.
또 신기한 것은 아래 타입에는 Object, 즉 객체 타입으로 표시가 되는데 이는 브라우저에서 인식되는 자바스크립트의 세계에서는 Enum이라는 정의는 존재하지 않는다.
따라서 타입스크립트 코드를 자바스크립트로 돌리는 과정에서 객체로 재정의 되는 것으로 보인다.
위에서 console로 enum 전체를 호출 해보았는데 이렇게 한번 써보면 어떻게 될까
console.log(SubjectEnum[1]);
console.log(SubjectEnum["영어"]);
결과는 과학
, 4
순으로 나오게 된다.
이게 왜 신기하냐면, 바로 역매핑이 된다는 것이다.
우리가 예를 들어서 const 과학 === 1
이라고 정의했다면, "과학" 이라는 값을 찍으면 1이 나올 것이다.
하지만 이 1이라는 값이 어떤 변수에 저장되어있는지 역추적은 불가할 것이다. 왜냐면 과학에게 1을 할당하였지 1에게 과학을 할당한 것이 아니니까.
그렇지만 Enum타입은 역매핑을 제공하여 Id값과 Key 값을 모두 찾아준다.
필자같은 경우에는 드롭다운으로 데이터 명을 클릭하면 Enum을 통해 Id값으로 변환하여 Payload에 넘겨 서버에 전송, 추후에 데이터를 받아올 때 역매핑하여 해당 값을 다시 텍스트화 시키는 식으로 사용하였던 경험이 있다.
아아주~~ 편했다. 심지어 enum.ts라는 파일로 따로 빼놓아서 필요한 컴포넌트에 꺼내서 쓸 수 있게 해놓았기에 코드 가독성도 나름 괜찮았다.
위에서 말했다시피 자바스크립트에는 애시당초에 Enum이란 정의가 존재하지 않는다. 그렇기 때문에 자바스크립트로 돌리는 작업 시에, Tree-shaking이 되지 않는다는 것이다.
라인 테크 블로그에서 예시로 보여준 코드를 인용해 예시 코드를 짜보았다.
export enum Subject {
과학 = 1,
수학 = 2,
국어 = 3,
영어 = 4,
}
// Javascript 로 트랜스파일링
export var Subject;
(function (Subject) {
Subject["과학"] = 1;
Subject["수학"] = 2;
Subject["국어"] = 3;
Subject["영어"] = 4;
})(Subject || (Subject = {}));
위에서 사용했던 과목 enum을 트랜스파일링 해보았다.
JavaScript에 없는 것을 구현하기 위해 TypeScript 컴파일러는 해당 기능을 즉시실행함수를 포함한 코드로 생성한다고 한다.
하지만 Webpack 같은 번들러는 즉시실행함수를 해당 파일에서 사용하지 않는 코드라고 판단할 수 없기에 트리 쉐이킹이 되지 않는다고 한다. 그렇게 되면 불필요한 코드도 최종파일에 담기기에 로드에도 딜레이가 될 것이다.
그래서 라인 팀에서 추천하는 대안은 Union Types로 객체를 선언하되, 변경이 안되는 객체로 추론하는 것이다.
타입스크립트 측에서도 고집부리면서 enum을 사용할 것이 아니면 as const
를 객체에 활용하는 것을 추천하는 편이다.
만약 쓴다면 enum Subject
는 아래와 같이 되겠지.
export const Subject = {
과학: 1,
수학: 2,
국어: 3,
영어: 4,
} as const;
그런데 그냥 객체로 사용해도 될 것 같은데 as const
를 마지막에 붙여 타입추론을 거쳐야되는지 궁금했다. 우리가 enum을 염두해 둔 것도 기획에서 나온 변하지 않는 고정값을 객체로 활용하기엔 쓰기요소를 제어하고 싶기에 읽기 전용인 열거형 타입을 활용한 것이라 생각한다. 그래서 const 타입을 추론한 버전과 안 쓴 버전을 비교하면 바로 알 수 있다.
// 일반 객체
const Subject: {
과학: number;
수학: number;
국어: number;
영어: number;
}
// as const
const Subject: {
readonly 과학: 1;
readonly 수학: 2;
readonly 국어: 3;
readonly 영어: 4;
}
읽기 전용 타입이 있는 것을 보아 후자가 enum의 역할을 대신 할 수 있는 것으로 볼 수 있다!
Reference
한눈에 보는 타입스크립트
TypeScript enum을 사용하지 않는게 좋은 이유를 Tree-shaking 관점에서 소개합니다.
enum으로 보는 원시 타입과 참조 타입