Language/TypeScript

[TypeScript] Class vs Interface vs Type 차이점 정리

eess 2024. 8. 7. 17:36

 

JavaScript Class vs TypeScript Class

1. 속성 초기화 코드를 생략할 수 있다.

  • TS Class는 생성자의 매개변수 타입 선언과 동시에 접근 제어자(+readonly)만 사용하면 속성 초기화를 할 수 있습니다.
  • 이때는 속성이 public이어도 명시해야 합니다.
// JS
class User {
  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }
}
// TS
// 컴파일하면 위 JS 코드와 동일합니다.
class User {
  constructor(private firstName: string, private lastName: string) {}
}

 

2. 접근 제한자(access modifier)를 지원한다.

  • 접근 제한자는 TS 스펙입니다. 따라서 컴파일하면 사라집니다.
  • JS에서는 private과 같은 용도로 # prefix를 붙입니다.
  • TS 접근 제한자에는 public, protected, private이 있습니다.
    • public : 어디서나 자유롭게 접근 가능
    • protected : 내 클래스를 상속한 자식 클래스 내에서 까지만 접근 가능
    • private : 내 클래스에서만 접근 가능
  • JS, TS 모두 접근 제한자를 생략하면 기본적으로 public입니다.

 

3. 추상 클래스(abstract class)를 지원한다.

  • 추상 클래스는 TS 스펙입니다. 컴파일하면 abstract 워드가 사라진 일반 클래스가 됩니다.
  • 상속받는 클래스가 어떻게 동작해야할 지 알려주기 위한 클래스로, interface와 유사합니다.
    • interface가 클래스나 객체의 뼈대를 만든다면, 추상 클래스는 클래스 전용으로 클래스의 뼈대를 만드는 역할입니다.
  • 하지만 abstract class로는 직접 인스턴스를 만들 수 없습니다. extends 키워드로 자식 클래스에 상속시켜야 합니다.
  • 속성이나 메서드 앞에 abstract 키워드를 붙여서 나를 상속하는 다른 클래스에서 이 속성이나 메서드를 구현하게 합니다.
    • 추상 메서드는 추상 클래스에 선언되는 메서드로, call signature만 작성합니다.
    • 추상 메서드가 있는 경우, 추상 클래스를 상속받는 자식 클래스는 추상 메서드를 반드시 구현해야 합니다.
abstract class User {
  constructor(protected firstName: string, protected lastName: string) {}

  abstract getFullName(): void;
}

class Player extends User {
  constructor(private firstName: string, private lastName: string) {}

  getFullName() {
    return `${this.firstName} ${this.lastName}`;
  }
}
const player = new Player("Timothee", "Chalamet");
player.getFullName();

 


Interface

  • interface는 클래스나 객체의 모양을 만들어줍니다.
  • interface는 TS 스펙으로, 컴파일하면 사라집니다.
  • interface는 선언을 병합할 수 있습니다.
interface User {
  firstName: string
}

interface User {
  lastName: string
}

interface User {
  health: number
}

const user: User {
  firstName: "Timothee",
  lastName: "Chalamet",
  health: 10
}
  • extends 키워드로 interface를 확장하여 새로운 interface를 만들 수 있습니다.
interface Animal {
  name: string;
}

interface Bear extends Animal {
  honey: boolean;
}

const bear = getBear();
bear.name;
bear.honey;

 


Type

  • type은 객체의 모양을 만들거나, 특정 값을 가지도록 제한할 수도 있습니다.
  • type은 TS 스펙으로, 컴파일하면 사라집니다.
  • type은 타입 별칭(type alias)을 만들 수 있습니다.
type Health = 1 | 5 | 10;
type Team = "red" | "yellow" | "blue";

type Player = {
  healthBar: number;
  team: Team;
};

const player: Player = {
  healthBar: 10,
  team: "blue",
};
  • & 로 type을 확장하여 새로운 type을 만들 수도 있습니다.
type Animal = {
  name: string;
};

type Bear = Animal & {
  honey: boolean;
};

const bear = getBear();
bear.name;
bear.honey;

 


Abstract class vs Interface

공통점

  • 둘 다 클래스의 모양을 정의할 수 있습니다.

차이점

  • abstract class는 컴파일하면 일반 클래스가 되고, interface는 컴파일하면 사라집니다.
  • 클래스는 다중 상속을 지원하지 않으므로 하나의 abstract class만 상속(extends)할 수 있고, interface는 여러 개를 구현(implements)할 수 있습니다.
  • abstract class는 추상 메서드가 아닌 일반 메서드에는 구현을 포함할 수 있지만, interface는 메서드에 구현을 포함할 수 없고 call signature만 써야 합니다.

결론

  • 클래스의 모양을 정의할 때 공통된 기능을 가지도록 구현 메서드를 제공한다면 abstract class를 사용합니다.
  • 그렇지 않다면 컴파일하면 사라지는 interface를 사용하길 권장합니다.

 


Interface vs Type

공통점

  • 둘 다 객체의 모양을 정의할 수 있습니다.
  • 컴파일하면 사라집니다.
  • abstract class를 대체할 수 있습니다.
type PlayerA = {
  firstName: string;
};

interface PlayerB {
  firstName: string;
}

class User implements PlayerA {
  constructor(public firstName: string) {}
}
// 또는
class User implements PlayerB {
  constructor(public firstName: string) {}
}

차이점

  • 정의하는 방식이 다릅니다.
    • interface Y { }
    • type X = { }
  • 확장 방식이 다릅니다.
    • interface A extends B { }
    • type A = B & { }
  • interface는 여러 번의 선언을 병합할 수 있지만, type은 선언을 병합할 수 없습니다.
  • type은 특정 값으로 타입을 제한하는 경우에도 사용할 수 있어 활용도가 좀 더 높습니다.

결론

  • interface는 객체 지향 프로그래밍의 개념을 활용해서 디자인되었고, type은 좀 더 유연하게 사용할 수 있습니다.
  • 클래스나 객체의 모양을 정의하고 싶으면 interface, 이외에는 타입 별칭이나 특정값으로 타입을 제한하는 경우 type 사용을 권장합니다.

 

참고