Engineering note · JS/TS

TypeScript에서 type, interface, enum을 선택하는 기준

2026년 03월 12일이현수
태그typescripttypeinterfaceenumas-const

객체 계약에는 interface, 조합과 유니온에는 type을 쓰고, 런타임 상수는 enum과 as const 중 요구사항에 맞게 고르는 기준을 정리한다.

TypeScript를 쓰다 보면 객체 모양을 만들 때는 typeinterface 중 무엇을 써야 하는지, 상태 코드처럼 정해진 값을 만들 때는 enum을 써야 하는지 자주 고민하게 된다. 문법을 통일하는 것도 중요하지만, 각 선언이 타입 검사에만 남는지 런타임 JavaScript까지 남는지를 구분하면 선택이 쉬워진다.

객체 형태

ts
interface User {
  id: string;
  name: string;
}

type UserPreview = {
  id: string;
  name: string;
};

interface와 객체 형태의 type은 비슷하게 사용된다. TypeScript는 값이 선언 이름과 일치하는지보다 필요한 프로퍼티를 갖는지 확인하는 구조적 타입 시스템이라서, 위 두 방식은 User 형태를 설명하는 데 큰 차이가 없다.

ts
interface Session {
  userId: string;
}

interface Session {
  expiresAt: string;
}

const session: Session = {
  userId: "user-1",
  expiresAt: "2026-07-13T12:00:00Z",
};

대신 interface는 객체 구조를 선언하고 extends로 확장한다. 같은 이름의 interface를 다시 선언하면 프로퍼티가 병합되는 declaration merging도 가능하다.

ts
type RequestState = "idle" | "loading" | "success" | "error";

type ApiResult<T> =
  | { ok: true; data: T }
  | { ok: false; message: string };

type ClickHandler = (event: MouseEvent) => void;

반면 type은 객체뿐 아니라 유니온, 튜플, 원시 값, 함수 시그니처에도 이름을 붙일 수 있다. 이미 만든 타입을 교차 타입으로 조합하는 것도 자연스럽다.

TypeScript 공식 문서는 대부분의 경우 둘 중 하나를 고를 수 있다고 설명한다. 팀에 별도 규칙이 없다면 객체의 공개 계약에는 interface를 먼저 쓰고, 유니온·튜플·조건부 타입처럼 type만 가능한 표현이 필요할 때 type을 쓰는 기준이 실용적이다.

같은 이름의 속성 유니온

ts
type HasStringId = { id: string };
type HasNumberId = { id: number };

type ImpossibleId = HasStringId & HasNumberId;
// ImpossibleId의 id는 string과 number를 모두 만족해야 하므로 never가 된다.

interface extends는 부모와 자식에 같은 이름의 프로퍼티가 있을 때 호환되는 타입을 요구한다. 반대로 교차 타입(&)도 “둘 중 하나”가 아니라 두 조건을 모두 만족해야 한다.

이름이 같지만 의미가 다른 값을 하나로 합쳐야 한다면 타입 선언으로 해결하려 하기보다, 프로퍼티 이름이나 데이터 모델을 먼저 나누는 편이 낫다. type의 교차 타입이 충돌을 자동으로 유니온으로 바꿔 준다고 생각하면 실제 사용 시 never를 만나기 쉽다. 이 차이는 객체 타입 공식 문서에서도 interface 확장과 intersection의 충돌 처리 차이로 설명한다.

enum은 타입 선언이면서 런타임 객체다

ts
enum LogLevel {
  Error = "ERROR",
  Warn = "WARN",
  Info = "INFO",
}

function writeLog(level: LogLevel) {
  console.log(level);
}

enum은 TypeScript에만 있는 문법이다. 일반 enum은 컴파일 후에도 객체로 남으므로, 값 목록을 런타임에 전달하거나 멤버 이름으로 접근해야 할 때는 편리하다.

사용하지 않은 enum 객체가 Rollup 결과에 남는 화면

실무에서는 enum 사용을 지양해야 한다는 목소리가 꾸준히 나오고 있다.

가장 큰 이유는 트리 쉐이킹(Tree-shaking) 문제다. TypeScript 코드가 JavaScript로 컴파일될 때, enum은 JavaScript에 없는 문법이므로 이를 흉내 내기 위해 즉시 실행 함수(IIFE) 형태로 변환된다.

여기서 Rollup이나 Webpack 같은 번들러는 IIFE 내부에서 어떤 객체가 조작되는지 정적 분석으로 판단하기 어렵다. 즉, 해당 enum을 프로젝트 어딘가에서 import만 해두고 실제로는 쓰지 않더라도 번들러는 이를 사용하는 코드로 간주해 번들에 포함할 수 있다. 결과적으로 불필요한 코드 양이 증가한다.

숫자 enum은 값에서 이름으로 찾는 역방향 매핑까지 생성한다. 문자열 enum은 역방향 매핑을 만들지 않지만, 둘 다 일반 enum이라면 JavaScript 런타임에 객체가 존재한다. TypeScript enum 문서를 보면 enum이 함수에 전달할 수 있는 실제 객체라는 점도 확인할 수 있다.

이 특성은 장점이자 비용이다. 단순히 고정된 문자열이나 숫자 집합이 필요할 뿐인데 enum 객체와 접근 코드를 꼭 만들 필요가 있는지는 별도로 판단해야 한다. 특히 번들 크기를 줄여야 하는 라이브러리나 여러 작은 모듈에서는 실제 생성된 JavaScript를 한 번 확인하는 게 좋다.

Enum을 대체하는 확실한 방법

이러한 enum의 단점을 피하면서 동일한 효과를 내기 위해 두 가지 대안을 사용할 수 있다.

const enum 사용

enum 선언 앞에 const를 붙이면 TypeScript는 값을 변환할 때 즉시 실행 함수를 만들지 않고 해당 상수가 사용된 곳에 값을 직접 치환한다.

ts
const enum Direction {
  Up,
  Down,
}

let dir = Direction.Up;

// 컴파일된 JS
let dir = 0 /* Direction.Up */;

코드가 가벼워지고 트리 쉐이킹이 가능해진다. 다만 런타임에 객체가 존재하지 않기 때문에 Object.keys() 같은 메서드로 순회할 수 없으며, 양방향 매핑도 사용할 수 없다는 제약이 있다.

as const와 객체 리터럴의 조합

실무에서 enum을 대체하는 방법으로 객체 리터럴과 as const를 조합할 수 있다.

보통 객체를 const로 선언해도 내부 속성값은 변경 가능한 string, number 타입으로 추론된다. 하지만 객체 뒤에 as const를 붙이면 객체의 모든 속성이 readonly가 되며, 값 자체가 리터럴 타입으로 고정된다.

ts
const HTTPRequestErrorCodeObject = {
  UNAUTHORIZED: 401,
  FORBIDDEN: 403,
  NOT_FOUND: 404,
  INTERNAL_SERVER_ERROR: 500,
} as const;

export type HTTPRequestErrorCode =
  typeof HTTPRequestErrorCodeObject[keyof typeof HTTPRequestErrorCodeObject];

이렇게 하면 HTTPRequestErrorCode401 | 403 | 404 | 500이라는 리터럴 유니온 타입으로 좁혀진다.

이 방식은 enum 변환을 위한 IIFE를 생성하지 않으며, 필요하다면 Object.keys()Object.values()를 사용해 런타임에 상수 목록을 순회할 수도 있다. 타입과 값 저장소를 분리해서 사용해야 한다는 약간의 번거로움이 있지만, IDE의 인텔리센스 지원과 번들 최적화를 함께 고려할 수 있다.

선택 기준

정답은 하나가 아니지만, 다음 기준으로 시작하면 팀 규칙을 만들기 쉽다.

필요우선 선택
확장 가능한 객체 계약, 선언 병합interface
유니온·튜플·함수·조건부 타입type
런타임 객체와 enum 전용 동작이 꼭 필요함enum
런타임 값 목록과 리터럴 유니온이 모두 필요함as const 객체 + typeof
앱 내부의 작은 상수를 인라인하고 싶음제약을 확인한 뒤 const enum

좋아요와 댓글

댓글 남기기

댓글 0개

댓글을 불러오는 중입니다.