Skip to content

9. 타입 호환(Type Compatibility)


1. 타입 호환이란?

  • 타입 호환이란 타입스크립트 코드에서 특정 타입이 다른 타입에 잘 맞는지를 의미한다.
  • 예를 들면 다음과 같은 코드를 의미한다.


interface Ironman {
  name: string;
}

class Avengers {
  name: string;
}

let i: Ironman;
i = new Avengers();


  • C#이나 Java였다면 위 코드에서 에러가 날 것이다.
  • 왜냐하면 Avengers 클래스가 명시적으로 Ironman 인터페이스를 상속받아 구현하지 않았기 때문이다.


  • 하지만 위와 같은 코드가 타입스크립트에서 정상적으로 동작하는 이유는 자바스크립트의 작동 방식과 관련이 있다.
  • 기본적으로 자바스크립트는 객체 리터럴이나 익명 함수 등을 사용하기 때문에 명시적으로 타입을 지정하는 것보다는 코드의 구조 관점에서 타입을 지정하는 것이 더 잘 어울린다.


2. 구조적 타이핑 예시

  • 구조적 타이핑이란 코드 구조 관점에서 타입이 서로 호환되는지의 여부를 판단하는 것이다.


interface Avengers {
  name: string;
}

let hero: Avengers;

// 타입스크립트가 추론한 y의 타입은 { name: string; location: string; }이다.
let capt = { name: "Captain", location: "Pangyo" };
hero = capt;


  • 위 코드에서 capthero 타입에 호환될 수 있는 이유는 capt의 속성 중에 name이 있기 때문이다.
  • Avengers 인터페이스에서 name 속성을 갖고 있기 때문에 captAvengers 타입에 호환될 수 있다.


  • 함수를 호출할 때도 마찬가지이다.


interface Avengers {
  name: string;
}

function assemble(a: Avengers) {
  console.log("어벤져스 모여라", a.name);
}

let capt = { name: "Captain", location: "Pangyo" };
assemble(capt);


  • capt 변수에 이미 name 속성뿐만 아니라 location 속성도 있기 때문에 assemble 함수의 호출 인자로 넘길 수 있다.


3. Soundness란?

  • 타입스크립트는 컴파일 시점에 타입을 추론할 수 없는 특정 타입에 대해서 일단 안전하다고 보는 특성이 있다.
  • 이런 것을 "들리지 않는다(It is said to not be sound)"라고 표현한다.


4. 이넘 타입 호환 주의 사항

  • 이넘 타입은 number 타입과 호환되지만 이넘 타입끼리는 호환되지 않는다.


enum Status {
  Ready,
  Waiting,
}
enum Color {
  Red,
  Blue,
  Green,
}

let status = Status.Ready;
status = Color.Green; // Error


5. 클래스 타입 호환 주의 사항

  • 클래스 타입은 클래스 타입끼리 비교할 때 스태틱 멤버와 생성자를 제외하고 속성(멤버 변수)만 비교한다.


class Hulk {
  handSize: number;
  constructor(name: string, numHand: number) {}
}

class Captain {
  handSize: number;
  constructor(numHand: number) {}
}

let a: Hulk = new Hulk("Hulk", 2);
let s: Captain = new Captain(2);

a = s;
s = a;


6. Generics

  • 제네릭은 제네릭 타입 간의 호환 여부를 판단할 때 타입 인자 <T>가 속성에 할당되었는지를 기준으로 한다.


interface Empty<T> {}

let x: Empty<number> = {};
let y: Empty<string> = {};

x = y;


  • 위 인터페이스는 일단 속성(멤버 변수)이 없기 때문에 xy는 같은 타입으로 간주된다.
  • 그런데 만약 다음과 같이 인터페이스에 속성이 있어서 제네릭의 타입 인자가 속성에 할당된다면 얘기는 다르다.


interface NotEmpty<T> {
  data: T;
}

let x: NotEmpty<number> = { data: 1 };
let y: NotEmpty<string> = { data: "a" };

x = y; // error


  • 인터페이스 NotEmpty에 넘긴 제네릭 타입 <T>data 속성에 할당되었으므로 xy는 서로 다른 타입으로 간주된다.

References