TypeScriptEasy#09
TypeScript에서 interface와 type의 차이점을 설명해주세요.
#TS#타입#인터페이스
힌트
선언 병합, 유니온/인터섹션, 확장 방식의 차이를 생각해보세요.
정답 및 해설
TypeScript에서 interface와 type의 차이점을 설명해주세요.
TypeScript에서 객체의 형태(shape)를 정의할 때 interface와 type 두 가지 방법을 사용할 수 있습니다. 둘은 많은 경우 서로 대체 가능하지만, 몇 가지 중요한 차이점이 있습니다. 이 차이를 이해하면 상황에 맞는 도구를 선택할 수 있습니다.
핵심 차이 요약
| 특성 | interface | type |
|---|---|---|
| 선언 병합 | 가능 | 불가 |
| 상속 | extends | & (인터섹션) |
| 유니온 타입 | 불가 | 가능 (|) |
| 원시 타입 별칭 | 불가 | 가능 |
| 튜플 | 불가 (단독으로) | 가능 |
| 조건부 타입 | 불가 | 가능 |
| 계산된 프로퍼티 | 제한적 | 가능 |
| 주 용도 | 객체 타입, 클래스 계약 | 복잡한 타입 조합 |
기본 문법 비교
// interface
interface User {
id: number;
name: string;
email?: string; // 선택적 프로퍼티
readonly createdAt: Date; // 읽기 전용
}
// type
type User = {
id: number;
name: string;
email?: string;
readonly createdAt: Date;
};
이 경우 두 방식은 완전히 동일하게 동작합니다.
차이점 1: 선언 병합 (Declaration Merging)
interface는 같은 이름으로 여러 번 선언하면 자동으로 합쳐집니다. type은 같은 이름으로 재선언하면 에러가 발생합니다.
// interface — 선언 병합
interface Window {
myCustomProperty: string;
}
interface Window {
anotherProperty: number;
}
// 최종적으로 Window는 두 선언이 합쳐진 타입
const w: Window = {
myCustomProperty: 'hello',
anotherProperty: 42,
// ... (기존 Window 프로퍼티들)
};
// type — 재선언 불가
type Point = { x: number };
// type Point = { y: number }; // 에러: Duplicate identifier 'Point'
선언 병합은 라이브러리의 타입 확장에 유용합니다.
// 예: Express의 Request 타입에 user 프로퍼티 추가
declare global {
namespace Express {
interface Request {
user?: { id: string; role: string };
}
}
}
차이점 2: 상속 방식
interface — extends
interface Animal {
name: string;
speak(): string;
}
interface Pet extends Animal {
owner: string;
}
interface Dog extends Pet {
breed: string;
}
// 다중 상속도 가능
interface ServiceDog extends Dog, Animal {
certification: string;
}
const myDog: Dog = {
name: '뽀삐',
owner: 'Alice',
breed: '말티즈',
speak: () => '멍멍!',
};
type — 인터섹션(&)
type Animal = {
name: string;
speak(): string;
};
type Pet = Animal & {
owner: string;
};
type Dog = Pet & {
breed: string;
};
const myDog: Dog = {
name: '뽀삐',
owner: 'Alice',
breed: '말티즈',
speak: () => '멍멍!',
};
interface extends type, type & interface 모두 가능
type HasId = { id: number };
interface User extends HasId {
name: string;
}
type Admin = User & { role: 'admin' };
차이점 3: type만 가능한 것들
유니온 타입
// type만 유니온 정의 가능
type Status = 'pending' | 'fulfilled' | 'rejected';
type StringOrNumber = string | number;
type NullableUser = User | null;
// interface로는 불가
// interface Status = 'pending' | 'fulfilled' | 'rejected'; // 에러
원시 타입 별칭
type UserId = string;
type Timestamp = number;
type Callback = (event: Event) => void;
튜플 타입
type Coordinate = [number, number];
type RGB = [number, number, number];
type Entry = [string, unknown];
const point: Coordinate = [37.5, 127.0];
조건부 타입
type IsString<T> = T extends string ? 'yes' : 'no';
type Result1 = IsString<string>; // 'yes'
type Result2 = IsString<number>; // 'no'
type Flatten<T> = T extends Array<infer Item> ? Item : T;
type Str = Flatten<string[]>; // string
type Num = Flatten<number>; // number
매핑 타입
type Optional<T> = {
[K in keyof T]?: T[K];
};
type Readonly<T> = {
readonly [K in keyof T]: T[K];
};
클래스와의 관계
interface는 클래스가 구현(implements)할 계약을 정의하는 데 적합합니다. type도 implements에 사용할 수 있지만, 유니온 타입은 사용 불가합니다.
interface Serializable {
serialize(): string;
deserialize(data: string): void;
}
class UserModel implements Serializable {
constructor(private name: string) {}
serialize(): string {
return JSON.stringify({ name: this.name });
}
deserialize(data: string): void {
const parsed = JSON.parse(data);
this.name = parsed.name;
}
}
실무 가이드라인
언제 interface를 사용하나?
- 객체의 형태(shape)를 정의할 때
- 클래스가 구현할 계약을 정의할 때
- 라이브러리/모듈의 공개 API 타입을 정의할 때 (선언 병합 활용)
// API 응답 타입
interface ApiResponse<T> {
data: T;
status: number;
message: string;
}
// 컴포넌트 Props
interface ButtonProps {
label: string;
onClick: () => void;
variant?: 'primary' | 'secondary';
}
언제 type을 사용하나?
- 유니온/인터섹션 타입이 필요할 때
- 원시 타입에 의미 있는 이름을 붙일 때
- 튜플, 조건부 타입, 매핑 타입을 사용할 때
- 함수 타입을 간결하게 정의할 때
type Direction = 'north' | 'south' | 'east' | 'west';
type EventHandler<T> = (event: T) => void;
type ApiError = { code: number; message: string } | null;
팀 내 일관성 유지
어떤 것을 선택하든 팀 내에서 일관된 규칙을 정하는 것이 중요합니다. 많은 프로젝트에서 다음 규칙을 따릅니다.
- 객체 타입 →
interface - 복잡한 타입 조합, 유니온, 원시 타입 별칭 →
type