iBetter Books
수정

Ch 06. 타입 별칭 vs 인터페이스

typeinterface — 둘 다 객체 타입을 정의하지만 동작 방식에 차이가 있습니다. 실무 선택 기준을 정리합니다.


type 별칭 기본

// 파일: src/type-alias.ts// 원시 타입에 이름 붙이기type UserId = number;type Email = string;// 객체 타입type User = {  id: UserId;  name: string;  email: Email;};// 유니온 타입type Status = "active" | "inactive" | "banned";// 인터섹션 타입type AdminUser = User & { permissions: string[] };// 함수 타입type Handler<T, R> = (input: T) => R;type StringHandler = Handler<string, void>;

type은 원시 타입, 유니온, 인터섹션, 튜플, 함수 타입 등 무엇이든 이름을 붙일 수 있습니다.


interface 기본

// 파일: src/interface-vs-type.tsinterface User {  id: number;  name: string;  email: string;}// interface는 객체 타입만 정의 가능// interface Status = "active" | "inactive";  // Error — 유니온은 type만 가능

핵심 차이 1 — 선언 병합 (Declaration Merging)

interface는 같은 이름으로 여러 번 선언하면 자동으로 병합됩니다. type은 불가능합니다.

// 파일: src/declaration-merging.tsinterface Window {  myPlugin: string;}interface Window {  anotherPlugin: number;}// 실제 Window는 두 선언이 합쳐진 것const w: Window = {  // ... 브라우저 기본 프로퍼티들 ...  myPlugin: "active",  anotherPlugin: 42,};

이 기능 덕분에 전역 타입 확장, 라이브러리 타입 보강(ambient declaration)이 가능합니다.

// 파일: src/ambient-extend.ts// Express Request에 userId 추가declare namespace Express {  interface Request {    userId?: number;  }}// 이후 req.userId로 접근 가능

핵심 차이 2 — 타입 표현 범위

// 파일: src/type-only-features.ts// type만 가능한 것들// 1. 유니온type Result = "ok" | "error";// 2. 인터섹션 (interface는 extends로 대체 가능하지만 type이 더 간결할 때가 있음)type Combined = TypeA & TypeB & TypeC;// 3. 튜플type Pair<T, U> = [T, U];// 4. 조건부 타입 (PART 04에서 다룸)type IsString<T> = T extends string ? true : false;// 5. 원시 타입 별칭type ID = string | number;// 6. typeof / keyof 활용type Keys = keyof SomeObject;type Config = typeof defaultConfig;

핵심 차이 3 — 에러 메시지

interface는 에러 메시지에서 이름이 표시됩니다. type의 복잡한 표현식은 인라인으로 풀어서 보여줄 수 있습니다.

// interface: 에러 메시지에 'User' 타입으로 표시됨interface User { id: number; name: string }// type 별칭: 복잡한 타입은 에러 메시지에 풀어서 표시됨type User = { id: number; name: string }

실무에서는 차이가 크지 않지만, IDE 툴팁 가독성에서 interface가 더 깔끔한 경우가 있습니다.


실무 선택 기준

// 파일: src/choosing-guide.ts// interface를 선택하는 경우// 1. 클래스가 구현(implements)해야 하는 계약interface Repository<T> {  findById(id: number): Promise<T | null>;  save(entity: T): Promise<T>;  delete(id: number): Promise<void>;}class UserRepository implements Repository<User> {  async findById(id: number) { /* ... */ return null; }  async save(user: User) { /* ... */ return user; }  async delete(id: number) { /* ... */ }}// 2. 외부에 공개되는 API 타입 (라이브러리 작성 시 확장 가능)export interface PluginOptions {  timeout?: number;  retries?: number;}
// type을 선택하는 경우// 1. 유니온/인터섹션 조합type ApiResponse<T> = { data: T; meta: ApiMeta } | { error: string };// 2. 유틸리티 타입 활용type ReadonlyUser = Readonly<User>;type PartialConfig = Partial<Config>;// 3. 함수 타입type EventHandler = (event: Event) => void;// 4. 튜플 타입type Coordinate = [number, number];// 5. 재귀 타입type JSONValue =  | string  | number  | boolean  | null  | JSONValue[]  | { [key: string]: JSONValue };

요약표

기능 type interface
객체 타입 정의 O O
유니온 타입 O X
인터섹션 타입 O △ (extends로 유사하게)
튜플 타입 O X
함수 타입 O O
선언 병합 X O
implements (클래스) O O
extends (확장) O O
조건부 타입 O X

실무 팀 컨벤션 예시

대부분의 팀은 아래 규칙 중 하나를 씁니다.

Option A — interface 우선

  • 객체 타입은 interface, 나머지(유니온, 함수 등)는 type
  • React 컴포넌트 props도 interface Props로 통일

Option B — type 우선

  • 모든 타입 정의에 type 사용
  • interface는 클래스 계약과 선언 병합이 필요한 곳만

둘 중 무엇을 선택하든 팀 내에서 일관성이 가장 중요합니다. TypeScript 공식 문서는 "확장 가능성이 필요하면 interface, 그 외엔 type"을 권장하지만, 강제 사항은 아닙니다.