면접에서 과제에 대해 위와 같은 질문을 받았는데, TypeScript에서는 enum 사용을 자제해야한다는 것만 어렴풋이 알고 있어서 정확히 대답하지 못했다. 이에 대해 공부하고 코드에 적용해보는 시간을 가졌다.
TypeScript에서 상수를 선언하는 방법에는 1. enum 2. const enum 3. as const가 있다.
세 가지 모두 값에 의미 있는 이름을 부여할 수 있다는 장점이 있다. 즉, 상수를 구체적으로 설명할 수 있는 값을 부여할 수 있다.
enum
- 장점
- [숫자형 enum] key-value 양방향 mapping이 가능하다.
- 연관된 값들의 집합을 하나로 묶어 접근 가능하다.
- [숫자형 enum이 아닌 경우] 값과 타입 모두 사용 가능해 Object.keys, Object.values를 사용해 순회 가능하다.
- 값으로 사용할 경우, IDE에서 코드 완성 기능을 통해 올바른 값을 선택할 수 있다.
- 타입으로 사용할 경우, 잘못된 값을 전달하면 컴파일 단계에서 에러를 발견할 수 있다.
- [숫자형 enum] key-value 양방향 mapping이 가능하다.
- 단점
- tree-shaking이 되지 않는다.
- js로 변환 시 IIFE(즉시 실행 함수)로 코드를 변환하기 때문에 번들러가 이를 tree-shaking하지 못한다.
- js로 변환 시 코드 양이 증가한다.
- tree-shaking이 되지 않는다.
enum COLORS {
Red = 'red',
Green = 'green',
Blue = 'blue',
PeRed = 'University of Pennsylvania red'
}
let currentColor = 'red'
const isPeRed = currentColor === COLORS.PeRed
console.log(isPeRed) // false
enum을 트랜스파일하면 아래와 같은 IIFE가 나와 코드 양이 증가한다.
"use strict";
var COLORS;
(function (COLORS) {
COLORS["Red"] = "red";
COLORS["Green"] = "green";
COLORS["Blue"] = "blue";
COLORS["PeRed"] = "University of Pennsylvania red";
})(COLORS || (COLORS = {}));
let currentColor = 'red';
const isPeRed = currentColor === COLORS.PeRed;
console.log(isPeRed); // false
const enum
- 장점
- tree-shaking이 된다.
- js로 변환 시 인라인으로 확장된다.
- js로 변환되면 매우 용량이 적다.
- key-value 양방향 mapping이 되지 않는다.
- 단점
- -isolatedModules 옵션 사용하면, 사용이 불가하다.
- js로 변환 시 인라인으로 확장된다. 따라서 긴 문자열을 할당하는 경우 불리할 수 있다.
- 인라인: 해당 코드가 호출되는 곳에 inlining할 대상 코드를 치환하여 컴파일한다. 아래와 같이 enum이 inline되어 처리 되었음을 의미하는 각주도 생성한다.
- CRA, Next에서 사용이 불가하고, babel 설정 추가로 필요하다.
- Object.keys, Object.values를 이용해 순회할 수 없다.
const enum COLORS {
Red = 'red',
Green = 'green',
Blue = 'blue',
PeRed = 'University of Pennsylvania red'
}
let currentColor = 'red'
const isPeRed = currentColor === COLORS.PeRed
console.log(isPeRed) // false
위의 코드는 js로 트랜스파일 시 아래와 같이 트랜스파일된다.
"use strict";
let currentColor = 'red';
const isPeRed = currentColor === "University of Pennsylvania red" /* COLORS.PeRed */;
console.log(isPeRed); // false
as const
- 장점
- tree-shaking이 된다.
- js로 변환 시 enum에 비해 적은 용량을 차지한다.
- 값과 타입 모두 사용 가능해 Objet.keys, Object.values로 순회 가능하다
- 단점
- key 타입을 주기 위해 union type을 만들어야 하는 번거로움이 있다.
const CountryMap = {
대한민국: 'SOUTH KOREA',
중국: 'CHINA',
일본: 'JAPAN',
미국: 'UNITED STATES',
북한: 'NORTH KOREA',
러시아: 'RUSSIA',
프랑스: 'FRANCE',
영국: 'ENGLAND',
} as const;
type CountryType = typeof CountryMap[keyof typeof CountryMap]
위와 같이 typeof로 CountryMap의 타입을 가져오고, keyof로 '대한민국', '중국'과 같은 CountryMap의 key 값을 얻는다. CountryMap에서 key에 해당하는 값을 찾으면 'SOUTH KOREA', "CHINA'와 같은 value가 나온다. 그리고 해당 값들의 타입을 typeof로 정의하는 방식이다.
이를 트랜스파일하면 아래와 같다.
"use strict";
const CountryMap = {
대한민국: 'SOUTH KOREA',
중국: 'CHINA',
일본: 'JAPAN',
미국: 'UNITED STATES',
북한: 'NORTH KOREA',
러시아: 'RUSSIA',
프랑스: 'FRANCE',
영국: 'ENGLAND',
};
적용
아래와 같이 국가 필터를 만들 시, 각 필터의 국가명을 상수로 선언해 사용할 수 있다.
필터에 다른 api 요청은 /api?glocations:("SOUTH KOREA", "CHINA")와 같이 영문명으로 보내야하기 때문에 국문명에 따른 영문명을 지정해 상수로 사용할 수 있다.
처음에는 아래와 같이 값과 타입을 각각 지정해 사용하였다.
const countryOptions = ['대한민국', '중국', '일본', '미국', '북한', '러시아', '프랑스', '영국'] as const;
export type Country = (typeof countryOptions)[number]; // 국문명 국가 타입
export type CountryMap = Record<Country, string>; // 국문명: 영문명 국가 타입
const countryMap: CountryMap = {
대한민국: 'SOUTH KOREA',
중국: 'CHINA',
일본: 'JAPAN',
미국: 'UNITED STATES',
북한: 'NORTH KOREA',
러시아: 'RUSSIA',
프랑스: 'FRANCE',
영국: 'ENGLAND',
};
위의 코드는 값과 타입을 따로 지정하여 불필요한 코드가 있고, 국문명은 오타가 나지 않도록 유니온으로 고정되어 있지만, 영문명의 경우는 평범한 string으로 값이 고정된 상수가 아니라는 점이 문제가 있다.
enum과 as const를 활용해 아래와 같이 변경해볼 수 있다.
const CountryMap = {
대한민국: 'SOUTH KOREA',
중국: 'CHINA',
일본: 'JAPAN',
미국: 'UNITED STATES',
북한: 'NORTH KOREA',
러시아: 'RUSSIA',
프랑스: 'FRANCE',
영국: 'ENGLAND',
} as const;
type CountryType = typeof CountryMap[keyof typeof CountryMap] // "SOUTH KOREA" | "CHINA" | "JAPAN" | "UNITED STATES" | "NORTH KOREA" | "RUSSIA" | "FRANCE" | "ENGLAND"
const country1 = CountryMap.대한민국 // "SOUTH KOREA"
const countryKRList = Object.keys(CountryMap) // ["대한민국", "중국", "일본", "미국", "북한", "러시아", "프랑스", "영국"]
as const와 유니온을 사용해 국문명 타입을 사용할 수 있고, Object.keys를 통해 국문명 값도 사용할 수 있다.
enum CountryMap {
대한민국 = 'SOUTH KOREA',
중국 = 'CHINA',
일본 = 'JAPAN',
미국 = 'UNITED STATES',
북한 = 'NORTH KOREA',
러시아 = 'RUSSIA',
프랑스 = 'FRANCE',
영국 = 'ENGLAND',
}
type countryKRType = keyof typeof CountryMap // "대한민국" | "중국" | "일본" | "미국" | "북한" | "러시아" | "프랑스" | "영국"
const country1 = CountryMap.대한민국 // "SOUTH KOREA"
const countryKRList = Object.keys(CountryMap) // ["대한민국", "중국", "일본", "미국", "북한", "러시아", "프랑스", "영국"]
enum을 사용한다면 위와 같이 사용할 수 있다.
해당 프로젝트는 번들러로 vite를 사용했고, 또 객체 key 타입 자체도 사용해야 해서 결국은 유니온을 사용해야했기 때문에 tree-shaking이 되는 as const를 사용하기로 했다.
결론
enum은 연관된 값들을 하나로 모아 추상화할 수 있어 직관적이며, 오타를 내지 않을 수 있는 간편한 장치이다. 하지만 tree-shaking이 되지 않는다는 큰 단점이 있다. 이는 vite 번들러 기반은 가능하다고 하니 사용하는 번들러도 확인해볼 필요가 있다. 또한 enum을 선언해두고 잘 사용한다면 문제가 없을 것으로 보인다. const enum의 경우, inlining으로 야기되는 문제가 많아 현재는 잘 사용하지 않는다. as const는 비록 union type을 선언해 사용해야 하는 번거로움은 있지만, enum과 유사하게 사용할 수 있고, tree-shaking이 된다는 장점이 있다. 프로젝트를 진행할 때 번들러나 enum 선언부의 크기 등을 고려해 결정해야겠다.
REF
https://engineering.linecorp.com/ko/blog/typescript-enum-tree-shaking
'FrontEnd > TypeScript' 카테고리의 다른 글
[TypeScript] React-hook-form과 제네릭 사용하기 (36) | 2024.02.04 |
---|---|
[TypeScript] 비동기 프로그래밍, 동시성과 병렬성 (0) | 2023.06.07 |
[TypeScript] 에러 처리 (0) | 2023.06.05 |
[TypeScript] 가변성(슈퍼타입/서브타입 파악하기) & 할당성 & 타입 넓히기 (0) | 2023.06.03 |
[TypeScript] 함수의 타입 (0) | 2023.06.03 |