이 글에서는 타입스크립트에서 상수를 선언하는 방법들과 각각의 장단점에 대해 다룹니다
타입스크립트에서 상수를 관리할 때 항상 as const 를 사용 해왔습니다
enum 은 안 좋다고만 알고 있었는데 최근 동료가 enum을 왜 사용하지 않는지에 대해 물어 봤는데
명확히 답변을 하지 못 했습니다 따라서 각 방법의 정확한 동작 원리와 장단점을 이해하고자 이 글을 작성하게 되었습니다
주의: 이 글의 번들 사이즈 비교는 특정 환경에서의 측정값이며, 실제 프로젝트에서는 번들러 설정, 압축 방식 등에 따라 크게 달라질 수 있습니다.
TypeScript의 enum은 연관된 상수들을 그룹화하는 기능입니다.
// 기본 사용법 (자동 증가)
enum Direction {
Up, // 0
Down, // 1
Left, // 2
Right // 3
}
// 시작값 지정
enum Direction {
Up = 1,
Down, // 2
Left, // 3
Right // 4
}
// 사용
const dir: Direction = Direction.Up;
console.log(dir); // 1
enum Direction {
Up = "UP",
Down = "DOWN",
Left = "LEFT",
Right = "RIGHT"
}
// 사용
const dir: Direction = Direction.Up;
console.log(dir); // "UP"
// TypeScript
enum Direction {
Up,
Down,
Left,
Right
}
// JavaScript로 컴파일된 결과
var Direction;
(function (Direction) {
Direction[Direction["Up"] = 0] = "Up";
Direction[Direction["Down"] = 1] = "Down";
Direction[Direction["Left"] = 2] = "Left";
Direction[Direction["Right"] = 3] = "Right";
})(Direction || (Direction = {}));
enum HttpStatus {
OK = 200,
BadRequest = 400,
NotFound = 404
}
// 이름 → 값
console.log(HttpStatus.OK); // 200
// 값 → 이름 (리버스 매핑)
console.log(HttpStatus[200]); // "OK"
enum Permission {
Read = "READ",
Write = "WRITE",
Delete = "DELETE"
}
// 모든 권한 목록 가져오기
const allPermissions = Object.values(Permission);
console.log(allPermissions); // ["READ", "WRITE", "DELETE"]
// 권한 체크 함수
function hasPermission(userPermissions: string[], required: Permission): boolean {
return userPermissions.includes(required);
}
enum Status {
Active = "ACTIVE",
Inactive = "INACTIVE"
}
function processStatus(status: Status) {
// status는 반드시 Status enum의 값이어야 함
}
processStatus(Status.Active); // OK
processStatus("ACTIVE"); // Error
enum Theme {
Light = "light",
Dark = "dark",
Auto = "auto"
}
// IDE에서 Theme. 입력 시 모든 옵션 표시
const userTheme = Theme.Dark;
// 5개의 enum 멤버 = 약 200-300 bytes
enum Colors {
Red = "#FF0000",
Green = "#00FF00",
Blue = "#0000FF",
Yellow = "#FFFF00",
Purple = "#800080"
}
// 사용하지 않아도 전체 객체가 번들에 포함됨
// colors.ts
export enum Colors {
Red = "red",
Green = "green",
Blue = "blue",
// ... 100개의 색상
}
// main.ts
import { Colors } from './colors';
console.log(Colors.Red); // 전체 enum 객체가 번들에 포함됨
// 참고: TypeScript 5.0+ 및 최신 번들러에서는 부분적 개선이 있으나
// 여전히 as const보다는 tree shaking이 제한적임
// (tree shaking 은 미사용 코드를 번들러가 제거해주는 기능으로 번들 크기를 줄여줍니다)
const enum은 컴파일 시점에 인라인되는 enum입니다.
const enum Direction {
Up,
Down,
Left,
Right
}
// 사용
const dir = Direction.Up;
// 컴파일 결과
const dir = 0 /* Up */;
// TypeScript
const enum HttpMethod {
GET = "GET",
POST = "POST",
PUT = "PUT",
DELETE = "DELETE"
}
const method = HttpMethod.GET;
// JavaScript (tsc로 컴파일 시)
const method = "GET" /* GET */;
// 주의: esbuild, SWC 등에서는 동작하지 않음
const enum MathConstants {
PI = 3.14159,
E = 2.71828
}
// 직접 값이 인라인되므로 객체 접근 오버헤드 없음
const area = radius * radius * 3.14159 /* PI */;
const enum Status {
Active = "ACTIVE",
Inactive = "INACTIVE"
}
function handleStatus(status: Status) {
// 여전히 타입 체크 가능
}
handleStatus(Status.Active); // OK
handleStatus("ACTIVE"); // Error
// tsconfig.json
{
"compilerOptions": {
"isolatedModules": true // Next.js, CRA 등 대부분의 모던 프레임워크 기본값
}
}
// 이 설정에서 const enum은 일반 enum처럼 동작
const enum Status {
Active = "ACTIVE"
}
// 컴파일 결과: 인라인되지 않고 객체 생성됨
var Status;
(function (Status) {
Status["Active"] = "ACTIVE";
})(Status || (Status = {}));
// TypeScript 컴파일러 (tsc)
const enum Status { Active = 1 }
console.log(Status.Active); // 1로 인라인됨
// esbuild
// const enum 인라인 미지원 (isolatedModules와 동일하게 동작)
// GitHub Issue #128에서 2020년부터 논의 중
// SWC
// 부분적 지원 (최신 버전에서 개선 중이나 여전히 불완전)
// 일부 케이스에서 빈 statement로 컴파일되는 버그 존재
// Babel
// const enum 문법 자체를 지원하지 않음
// babel-plugin-const-enum 같은 플러그인 필요
// Vite (esbuild 기반)
// const enum 인라인 미지원
// 라이브러리에서 const enum 내보내기 - 절대 금지!
// library/index.d.ts
export const enum LibEnum {
Value = 1
}
// app.ts
import { LibEnum } from 'library';
console.log(LibEnum.Value); // 컴파일 에러 또는 undefined
// TypeScript 팀 공식 권장사항:
// "라이브러리를 만들 때는 const enum을 사용하지 마세요"
const enum ErrorCode {
NotFound = 404,
ServerError = 500
}
// 런타임에서는 숫자만 보임
console.log(getError()); // 404 (어떤 에러인지 알기 어려움)
// 일반 enum은 리버스 매핑으로 확인 가능
console.log(ErrorCode[404]); // "NotFound"
// 빌드 도구를 변경할 때마다 const enum 동작이 달라질 수 있음
// tsc → esbuild: const enum이 일반 enum처럼 동작
// esbuild → SWC: 예상치 못한 컴파일 결과 가능
as const는 TypeScript 3.4에서 도입된 const assertion입니다.
// 기본 사용법
const Colors = {
Red: "#FF0000",
Green: "#00FF00",
Blue: "#0000FF"
} as const;
// 타입 추출
type Colors = typeof Colors[keyof typeof Colors];
// type Colors = "#FF0000" | "#00FF00" | "#0000FF"
// 사용
function setColor(color: Colors) {
console.log(color);
}
setColor(Colors.Red); // OK
setColor("#FF0000"); // OK (리터럴 값도 가능)
// 순수 JavaScript 객체이므로 누구나 이해 가능
const Status = {
Active: "active",
Inactive: "inactive"
} as const;
// JavaScript 개발자도 바로 이해 가능한 코드
// colors.ts
export const Colors = {
Red: "#FF0000",
Green: "#00FF00",
Blue: "#0000FF",
// ... 100개의 색상
} as const;
// main.ts
import { Colors } from './colors';
console.log(Colors.Red); // Red만 번들에 포함
const Config = {
api: {
baseUrl: "https://api.example.com",
timeout: 5000
},
features: {
darkMode: true,
notifications: false
}
} as const;
// 중첩된 타입 추출 가능
type ApiConfig = typeof Config.api;
type FeatureFlags = typeof Config.features;
// 유틸리티 타입과 조합
type ConfigKeys = keyof typeof Config;
type ConfigValues = typeof Config[ConfigKeys];
const Permissions = {
Read: "READ",
Write: "WRITE",
Delete: "DELETE"
} as const;
// 모든 JavaScript 기능 사용 가능
Object.keys(Permissions);
Object.values(Permissions);
Object.entries(Permissions);
// 동적 접근
const permission = "Read";
console.log(Permissions[permission]);
// Enum - 타입이 자동 생성
enum Status {
Active = "ACTIVE",
Inactive = "INACTIVE"
}
// As Const - 타입을 별도로 정의해야 함
const Status = {
Active: "ACTIVE",
Inactive: "INACTIVE"
} as const;
type Status = typeof Status[keyof typeof Status];
// 리팩토링 시 둘 다 관리해야 함
const Status = {
Active: "ACTIVE",
Inactive: "INACTIVE"
} as const;
// TypeScript는 막지만 런타임에서는 가능
(Status as any).Active = "MODIFIED"; // 런타임 에러 위험
// enum은 원천적으로 불가능
enum StatusEnum {
Active = "ACTIVE"
}
// StatusEnum.Active = "MODIFIED"; // 컴파일 자체가 불가능
const Status = {
Active: "ACTIVE",
Inactive: "INACTIVE"
} as const;
type Status = typeof Status[keyof typeof Status];
function handleStatus(status: Status) {
console.log(status);
}
// 의도와 다르게 리터럴 값 직접 사용 가능
handleStatus("ACTIVE"); // OK
handleStatus("RANDOM"); // Error
// 하지만 오타나 복사-붙여넣기 실수 위험
handleStatus("ACTIV"); // 컴파일 에러지만 실수하기 쉬움
// enum: "Active" 이름 변경 시 모든 참조 자동 업데이트
enum Status { Active = "ACTIVE" }
// as const: 객체 속성과 타입을 각각 수동으로 관리
const StatusValues = { Active: "ACTIVE" } as const;
type Status = typeof StatusValues[keyof typeof StatusValues];
실제 측정 결과를 바탕으로 한 번들 크기 비교:
// 1. Enum (10개 항목)
enum LargeEnum {
Option1 = "OPTION_1",
Option2 = "OPTION_2",
// ... 10개 항목
}
// 컴파일 후: 약 425 bytes
// Minified: 약 240 bytes
// Gzipped: 약 120-150 bytes
// 2. Const Enum (10개 항목)
const enum LargeConstEnum {
Option1 = "OPTION_1",
Option2 = "OPTION_2",
// ... 10개 항목
}
// 컴파일 후: 0 bytes (인라인됨)
// 주의: isolatedModules: true 환경에서는 일반 enum처럼 동작
// 3. As Const (10개 항목)
const LargeConst = {
Option1: "OPTION_1",
Option2: "OPTION_2",
// ... 10개 항목
} as const;
// Minified: 약 200 bytes
// Tree shaking 시: 사용한 속성만 포함
// 4. Union Type (언급되지 않은 대안)
type Options = "OPTION_1" | "OPTION_2" | /* ... */ "OPTION_10";
// 컴파일 후: 0 bytes (타입만 존재)
중요: 실제 번들 크기는 번들러 설정, 압축 알고리즘, 사용 패턴에 따라 크게 달라집니다. 몇 백 바이트 차이가 결정적 요인이 되는 경우는 드뭅니다.
// shared.ts
export enum Colors {
Red = "#FF0000",
Green = "#00FF00",
Blue = "#0000FF"
}
export const ColorsConst = {
Red: "#FF0000",
Green: "#00FF00",
Blue: "#0000FF"
} as const;
// main.ts
import { Colors, ColorsConst } from './shared';
console.log(Colors.Red); // 전체 Colors enum이 번들에 포함
console.log(ColorsConst.Red); // ColorsConst.Red만 번들에 포함
// 권장: enum (명확한 API 제공)
export enum LogLevel {
Debug = 0,
Info = 1,
Warning = 2,
Error = 3
}
// 피하세요: const enum (사용자 환경에서 문제 발생)
export const enum BadEnum { ... }
// 권장: as const (유연성과 tree shaking)
export const Theme = {
colors: {
primary: "#007bff",
secondary: "#6c757d"
}
} as const;
// 대안: Union Type (간단한 경우)
type Status = "active" | "inactive" | "pending";
// 권장: enum (기존 코드와의 호환성)
enum ApiErrorCode {
Unauthorized = 401,
Forbidden = 403,
NotFound = 404
}
실제 성능 차이는 미미합니다:
// 간단한 상수 집합에는 Union Type도 좋은 선택
type Direction = "up" | "down" | "left" | "right";
// 장점: 번들 크기 0, 타입 안전성 확보
// 단점: 런타임 값 없음, 자동완성 시 문자열 입력 필요
처음에는 단순히 enum은 나쁘다고만 생각했지만, 실제로는 각 방법마다 명확한 장단점과 사용 시나리오가 있었습니다.
실제 트레이드오프
빌드 도구별 const enum 지원 현황
핵심 교훈
1. 번들 크기 몇 백 바이트로 기술 결정을 하는 것은 과도한 최적화일 수 있습니다
2. 각 프로젝트의 요구사항과 제약사항을 고려하여 선택하세요
3. 팀의 개발 경험과 유지보수성이 더 중요한 경우가 많습니다
4. const enum은 빌드 도구 호환성 문제로 피하는 것이 좋습니다