이번 글에서는 enum 대신에 union type을 써야한다는 내용에 대해 써보겠습니다.
얻어갈 수 있는 내용🎯
- enum 타입의 컴파일 과정과 런타임에 미치는 영향
- Tree-shaking
- 굳이 enum 타입을 쓰는 방법
- enum 타입 말고 union type을 써야하는 이유
타입스크립트에서 정적 타입 자체는 런타임에 직접적인 영향을 주지 않습니다.🙅♂️
타입스크립트에서 타입 정보는 컴파일 타임에만 사용되고,
컴파일된 JavaScript 코드에서는 제거되는데요
이는 타입스크립트의 타입 시스템이 런타임에 존재하지 않는다는 것을 의미합니다.
즉, 런타임에는 모든 타입이 사라지고, 순수한 JavaScript 코드만 남게 된다는거죠.
그런데 말입니다.
enum과 같은 특정 타입스크립트 구문은 런타임에도 영향을 줄 수 있는 코드로 변환됩니다.🤯
예를 들어,
enum은 컴파일 과정에서 JavaScript 객체로 변환되므로,
이 객체는 런타임에 실제로 존재하게 됩니다.
정적 타입 정보가 아니라,
타입스크립트의 구조적 특성이 컴파일된 코드에 남아 있음을 의미하게 되는거죠.
이러한 변환이 때때로 Tree-shaking,
즉, 불필요한 코드를 제거하는 프로세스를 방해할 수 있습니다.
Tree-shaking이 뭐에요?? (나무 흔들기 ??)
Tree-shaking이란 간단하게 말해 사용하지 않는 코드를 삭제하는 기능을 말합니다.
나무를 흔들면 죽은 잎사귀들이 떨어지는 모습에서 따온 이름입니다.
Tree-shaking을 통해 export했지만
아무 데서도 import하지 않은 모듈이나 사용하지 않는 코드를 삭제해서
번들 크기를 줄여 페이지가 표시되는 시간을 단축할 수 있습니다.
그렇다면 enum은 뭔가요?? (이놈 ??)
enum은 TypeScript가 자체적으로 구현하는 기능입니다.
enum은 열거형 변수로정수를 하나로 합칠 때 편리한 기능입니다.
임의의 숫자나 문자열을 할당할 수 있으며
하나의 유형으로 사용해서 버그를 줄일 수 있습니다.
Tree-shaking은
ES2015 모듈 문법(import/export)을 사용하는 모듈에서 가장 효과적으로 작동합니다.
ES2015 모듈은 정적 구조를 가지고 있어서,
빌드 도구(예: Webpack, Rollup)가 사용되지 않는 코드를
쉽게 식별하고 제거할 수 있게 해줍니다.
하지만 TypeScript의 enum은 정적 구조를 갖지 못합니다.
enum은 객체로 변환되어 각 열거형 멤버에 대해 속성을 추가하는 방식으로 작성되는데요.
이 과정에서 생성된 코드는
Tree-shaking이 제대로 작동하지 않을 수 있는 부수 효과를 만들어낼 수 있습니다.
예를 들어, TypeScript에서 다음과 같은 enum을 정의했다고 가정해 보겠습니다.
// status.ts
export enum Status {
Active = 'ACTIVE',
Inactive = 'INACTIVE',
Pending = 'PENDING'
}
이 enum을 컴파일한 JavaScript 코드는 대략 다음과 같습니다.
// 컴파일된 JavaScript
export var Status;
(function (Status) {
Status["Active"] = "ACTIVE";
Status["Inactive"] = "INACTIVE";
Status["Pending"] = "PENDING";
})(Status || (Status = {}));
JavaScript에 존재하지 않는 것을 구현하기 위해
TypeScript 컴파일러는 IIFE(즉시 실행 함수)를 포함한 코드를 생성합니다.
하지만 Rollup과 같은 번들러는
IIFE를 '사용하지 않는 코드' 라고 판단할 수 없어서 Tree-shaking이 되지 않습니다.
결국 Status를 import하고 실제로는 사용하지 않거나 일부만 사용하더라도
최종 번들에는 포함되는 것입니다.
각 enum 멤버가 이 enum 객체에 속성으로 추가 되기 때문입니다.
다음은 enum을 사용하는 예시 코드입니다
// main.ts
import { Status } from './status';
console.log(Status.Active);
결과 번들에서는 Status enum의 모든 멤버가 포함되어 있을 겁니다.
Inactive와 Pending은 사용되지 않았지만,
Tree-shaking은 이를 번들에서 제거하지 못하기 때문이죠.
이와 대조적으로,
enum 대신 객체 리터럴이나 상수를 사용하면,
Tree-shaking이 더 효과적으로 작동할 수 있습니다.
예를 들어서 코드로 보여주자면,
// status.ts
export const ACTIVE = 'ACTIVE';
export const INACTIVE = 'INACTIVE';
export const PENDING = 'PENDING';
그리고 이를 불러와서 사용하는 쪽에서는,
// main.ts
import { ACTIVE } from './status';
console.log(ACTIVE);
이 경우, INACTIVE와 PENDING은 사용되지 않으므로
Tree-shaking에 의해 제거될 수 있습니다.
모듈 내에서 각 상수는 개별적으로 존재하므로,
사용되지 않는 것들은 최종 번들에서 제외될 수 있습니다.
이러한 이유로,
번들 크기를 최소화하고자 할 때는 TypeScript의 enum 사용을 피하고,
대신 객체 리터럴이나 상수를 사용하는 것이 좋습니다.
그러면 enum 은 무조건 나쁜걸까?
무조건이라는건 없죠.
타입스크립트 개발팀에서는
개발자 편의를 위해 enum 을 추가했을텐데요.
많은 언어에는 enum 이 내장되어 있죠.
만약에
자바스크립트에 enum 이 내장되어 있었다면
enum 을 적극적으로 사용하라고 했을겁니다.
enum 이 주는 편의 기능 중에 하나는 자동 완성인데,
Fruit. 을 입력하면 모든 속성을 나열해주죠.
우리는 속성 이름을 몰라도
Fruit 라는 단어만 기억하고 있으면 됩니다.
enum 을 사용하면서도
런타임에 영향을 주지 않는 한 가지 방법이 있는데요.
바로 const enum 을 사용하는 것입니다.
const enum Fruit {
Apple,
Banana,
Orange,
}
const enum 을 사용하면 컴파일 후에 객체가 생성되지 않습니다.
하지만 그럼에도 불구하고 enum 대신에 union type 을 쓰는걸 추천드립니다.
그 이유는 생산성과 관련이 있는데요,
enum 은 값을 입력하기 위해서는 import 코드를 작성해야 합니다.
하지만 union type 은 import 를 할 필요가 없죠.
enum 타입 예시
// enum 으로 정의한 경우 import 가 필요합니다
// status.enum.ts 파일
export enum Status {
Active = 'ACTIVE',
Inactive = 'INACTIVE',
Pending = 'PENDING'
}
// main.ts 파일
import { Status } from './status.enum';
function logStatus(status: Status) {
console.log(status);
}
logStatus(Status.Active); // 'ACTIVE'를 출력합니다.
union 타입 예시
// main.ts 파일
type Status = 'ACTIVE' | 'INACTIVE' | 'PENDING';
function logStatus(status: Status) {
console.log(status);
}
logStatus('ACTIVE'); // 'ACTIVE'를 출력합니다.
그리고 다음처럼 타입 정보를 알고 있는 상태라면
union type 도 enum type처럼 자동 완성이 됩니다.
결론 : enum 보단 union 타입 씁시다.
'개발 > TypeScript' 카테고리의 다른 글
TSC 의 컴파일 프로세스 (1) | 2024.04.01 |
---|---|
Any VS Unknown VS Never (0) | 2024.03.25 |
개발 블로그
포스팅이 좋았다면 "좋아요❤️" 누르기 !