전체 목록
TypeScriptEasy#09

TypeScript에서 interface와 type의 차이점을 설명해주세요.

#TS#타입#인터페이스
힌트

선언 병합, 유니온/인터섹션, 확장 방식의 차이를 생각해보세요.

정답 및 해설

TypeScript에서 interface와 type의 차이점을 설명해주세요.

TypeScript에서 객체의 형태(shape)를 정의할 때 interfacetype 두 가지 방법을 사용할 수 있습니다. 둘은 많은 경우 서로 대체 가능하지만, 몇 가지 중요한 차이점이 있습니다. 이 차이를 이해하면 상황에 맞는 도구를 선택할 수 있습니다.

핵심 차이 요약

특성interfacetype
선언 병합가능불가
상속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)할 계약을 정의하는 데 적합합니다. typeimplements에 사용할 수 있지만, 유니온 타입은 사용 불가합니다.

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