-
[TypeScript] enum+union으로 문자열 타입 상수화하기Studying/JavaScript 2023. 7. 14. 09:24
문제 발생
Portfolly 프로젝트 중 카테고리 드롭다운 컴포넌트를 만들다가 발생한 문제입니다.
메인 프로젝트에서 카테고리는 다음 다섯 가지로 고정 됩니다.
웹, 앱, 3D/애니메이션, 디자인/일러스트, 사진/영상
그러다보니 카테고리 관련 타입을 명시할 때 저 다섯 가지 외 값을 고정시키고 싶었습니다. 만약 타입을 string이라고만 명시할 경우, "배고파" 라는 값이 전달 돼도 동작 자체는 정상적으로 되기 때문입니다.
그래서 string 대신 다음과 같이 유니온으로 타입을 명시했습니다.
category: "웹" | "앱" | "3D/애니메이션" | "디자인/일러스트" | "사진/영상"
그런데 여러 파일에서 카테고리를 정의해야 했고, 매번 긴 코드를 반복해서 적어야 했습니다.
만약 카테고리를 하나 추가하고 싶을 경우 카테고리 타입을 받는 파일마다 찾아가 값을 추가해줘야 하는 상황이 되었습니다.
그래서 문자열 유니온을 상수처럼 가져다 쓰기로 했습니다.
일단 category 타입을 사용해야 하는 부분은 크게 두 가지였습니다.
- 카테고리명을 props로 받는 경우
- 프론트에 정적으로 저장한 카테고리별 태그 객체를 정의하는 경우
두 번째가 무슨 뜻이냐면, 백엔드에서 카테고리별 태그 값을 GET 요청하지 않고 그냥 프론트에 데이터를 저장해놓기로 해서 아래와 같은 categoryTags.ts 파일이 존재합니다. 이 객체의 타입을 명시하는 CategoryTags에도 카테고리명을 고정해야 했습니다.
import { CategoryTags } from '@/types'; export const categoryTags: CategoryTags = { "웹": { tags: [ { tagId: 1, name: 'javascript', }, ... ], }, "앱": { tags: [ { tagId: 6, name: 'android', }, ... ], }, ... }
Enum + Union으로 해결(No!!! 별로인 방법)
그때 생각난 게 Typescirpt의 enum 이었습니다.
"enum이란 문자열이나 숫자 변수를 그룹으로 묶어 사전 정의하는 것"입니다. 그룹과 사전 정의라는 부분이 지금 제가 원하는 것 그 자체이기 때문에 enum이야 말로 문제를 해결하기에 적합하다고 생각했습니다.
그래서 무작정 CATEGORY enum을 정의하였습니다.
enum CATEGORY { "웹", "앱", "3D/애니메이션", "디자인/일러스트", "사진/영상", }
그런데 enum은 다음과 같이 키 값을 뽑아쓸 수 있을 뿐이지 제가 원하는 것처럼 문자열(키) 유니온 타입을 상수처럼 쓸 수 없었습니다.
console.log(CATEGORY['3D/애니메이션'])
그렇다면 키 값을 뽑아 유니온 타입으로 만들어주면 됩니다.
객체의 키 값을 상수 타입으로 쓰고 싶다면 keyof typeof 를 붙이면 됩니다.
type CATEGORY_TYPE = keyof typeof CATEGORY;
- keyof - 키 값만 뽑아온다.
- typeof - 값을 타입으로 정의한다.
이렇게 정의하면 CATEGORY_TYPE에 마우스를 hover 했을 때,
제가 원하던 방식의 문자열 유니온 타입을 보여줍니다!!! 이제 저 CATEGORY_TYPE을 상수 타입으로 여기저기 넣어주기만 하면 됩니다. 먼 훗날 카테고리를 하나 더 늘이게 되더라도 enum 정의만 수정하면 되는 겁니다.
이로써 해당 상수 타입을 활용하여 다음과 같이 타입을 명확하면서도 깔끔하게 적을 수 있었습니다.
export const setCategory = (category: CATEGORY_TYPE) => ({ type: SET_CATEGORY, category });
export type CategoryTags = { [key in CATEGORY_TYPE]: { tags: Array<Tag>; }; };
그런데 Enum을 안 쓰는 게 좋다?
이렇게 다 만들고 뿌듯해하고 있었는데, 사실 더 나은 방법이 있었습니다.
export type CATEGORY_TYPE = 'web' | 'app' | '3danimation' | 'graphicdesign' | 'photo';
그냥 애초에 이렇게 Union Type으로 작성하면 되는 거였습니다.
이렇게 고치고 나니 왜 Enum이란 것에 꽂혀있었는지 저도 잘 모르겠습니다. 당시엔 번뜩이는 아이디어랍시고 Enum을 Union으로 바꾸자! 라고 생각했는데, 음... 조금 이상한 상태였던 것 같습니다.
더하여 Enum에는 몇가지 단점이 존재했습니다.
- TreeShaking이 안 되어 파일 크기가 커진다.
- 선언되지 않는 key 값의 접근을 허용한다.
enum은 Typescript에서 열거형 기능을 만들기 위해 생성된 기능입니다.
따라서 JS에는 enum이란 문법이 없기 때문에, JS로 트랜스파일 될 때 이를 구현하기 위해 Direction이라는 변수와 즉시 실행 함수의 형태로 변하게 됩니다.
일반적으로 rollup과 같은 번들러는 export했지만 import하지 않은 모듈이나, 불러와놓고 사용 안 한 코드가 있으면 트리 셰이킹으로 삭제해서 번들 크기를 줄입니다.
그런데 번들러 입장에서 즉시 실행 함수(IIFE)는 언제 쓰일지 모르는 코드기 때문에 '사용하지 않는 코드'라고 판단할 수 없어 트리셰이킹을 못 하게 됩니다.
이 문제를 해결하기 위해 const 키워드를 사용하는 방법이 있지만
(Typescript는 선언에 대한 코드는 생성하지 않으므로, JS로 컴파일 됐을 때 enum 구현체는 사라지고 실행되는 코드만 남아있습니다. 따라서 사용하지 않는 코드에 대한 트리 셰이킹이 가능해집니다.)
저의 경우 굳이 enum을 사용할 일이 아니었기 때문에 이 상황에서는 enum을 완전히 잊어버리기로 했습니다.
참고 사이트
'Studying > JavaScript' 카테고리의 다른 글
Property 'env' does not exist on type 'ImportMeta'.ts(2339) (0) 2023.08.16 DOMpurify를 사용하면 iframe 태그를 불러오지 못 하는 이유 (0) 2023.07.22 dangerouslySetInnerHTML 의 보안에 대하여 (0) 2023.07.13 공공 코로나 데이터 OpenAPI 연결 (0) 2022.08.30 [Socket.io] 웹소켓으로 그림판 채팅 만들기 (0) 2022.08.10