객체 타입 — interface vs type
두 가지 도구, 미묘하게 다른 성격. 선택 기준 한 줄로 정리합니다.
TypeScript 에서 객체에 타입을 붙이는 방법은 두 가지 — interface 와 type(타입 별칭) 입니다. 거의 같은 일을 하지만 미묘하게 다릅니다. 4편은 이 둘의 차이를 정확히 정리하고, "언제 어떤 쪽을 골라야 하나" 의 실전 기준을 줍니다.
기본 모양 — 거의 똑같다
// interface
interface User {
id: number;
name: string;
email?: string; // 옵션
readonly joinedAt: Date;
}
// type alias
type User = {
id: number;
name: string;
email?: string;
readonly joinedAt: Date;
};
// 사용은 동일
const u: User = { id: 1, name: "준성", joinedAt: new Date() };
객체의 모양(shape) 을 적는 게 전부라면 둘은 사실상 같습니다. 차이는 "확장" 과 "표현 범위" 에서 나옵니다.
확장하기 — extends vs &
// interface — extends
interface Admin extends User {
role: "admin" | "owner";
}
// type — & (인터섹션)
type Admin = User & {
role: "admin" | "owner";
};
// 둘 다 같은 결과
const a: Admin = { id: 1, name: "준성", joinedAt: new Date(), role: "admin" };
여러 개 확장도 가능합니다 — interface 는 콤마로, type 은 & 를 연이어:
interface C extends A, B { ... }
type C = A & B & { ... };
declaration merging — interface 만의 능력
// interface 는 같은 이름으로 여러 번 선언하면 합쳐집니다
interface Window {
customProp: string;
}
interface Window {
anotherProp: number;
}
// 결과: Window 가 두 속성 모두 가짐
// type 은 같은 이름으로 두 번 선언 불가
type X = { a: number };
type X = { b: number }; // ❌ Duplicate identifier 'X'
이 기능은 라이브러리의 글로벌 타입 확장(window, Express Request 등) 에 거의 유일하게 쓰입니다. 평소 코드에서는 의외성이 더 커서 쓰지 않습니다. 대부분의 경우 단점 으로 작용해요 — 다른 파일이 몰래 합쳐버릴 수 있으니까요.
type 만의 능력 — primitive·유니온·튜플
// interface 는 객체 모양만 표현 가능
// type 은 거의 모든 것을 표현 가능
type ID = string | number; // 유니온
type Pair = [string, number]; // 튜플
type Color = "red" | "green" | "blue"; // literal 유니온
type Fn = (x: number) => number; // 함수 시그니처
type Lazy<T> = () => T; // 제네릭 alias
// 더 깊은 표현 (12·13편에서 다룸)
type Keys<T> = keyof T;
type Maybe<T> = T | null | undefined;
이게 가장 큰 차이입니다. "객체가 아닌 타입에 이름을 붙이고 싶다" 면 type 이 유일한 답입니다.
선택 기준 — 한 줄짜리 가이드
실전 권장 (TS 공식 권장과 같음).
① 객체 모양 + extends → interface 선호 (클래스·DTO·도메인 모델).
② 유니온·튜플·함수 시그니처·고차 타입 → type.
③ 한 프로젝트 안에서는 일관성이 가장 중요. 팀이 type 만 쓰기로 했으면 type 만, interface 만 쓰기로 했으면 interface 만.
| 특징 | interface | type |
|---|---|---|
| 객체 모양 | ✅ | ✅ |
| 확장 | extends | & 인터섹션 |
| 유니온·튜플·함수 | ❌ | ✅ |
| declaration merging | ✅ (가끔 유용) | ❌ |
| 제네릭 | ✅ | ✅ |
| readonly·옵션 ? 표기 | ✅ | ✅ |
| implements (class) | ✅ 자연스러움 | ✅ (객체 타입 한정) |
readonly·옵션 — 둘 다 쓰는 핵심
interface Settings {
readonly version: string; // 한 번 정해지면 변경 금지
theme?: "light" | "dark"; // 옵션
}
const s: Settings = { version: "1.0" };
s.version = "1.1"; // ❌ readonly
s.theme = "dark"; // OK
인터페이스 vs 클래스의 implements. 클래스에 "이 인터페이스를 구현하라" 고 강제할 때 class User implements UserShape { ... } 처럼 씁니다. interface 가 자연스럽고, type 도 가능하지만 일부 환경에서 메시지가 덜 친절합니다. OOP 색이 진한 코드에서는 interface 가 익숙합니다.
5편 — 유니온과 인터섹션
A | B 와 A & B, 좁히기(narrowing), 그리고 진짜 유용한 구분 가능 유니온(discriminated union).
이전: 3편 함수 타입 · 현재: 4편 (입문) · 다음 → 5편 유니온·인터섹션 · 진행: 4/20