14. 타입 호환

CHOYEAH·2023년 11월 5일
0

TypeScript

목록 보기
14/23
post-custom-banner

14. 타입 호환

타입 호환이란 타입스크립트 코드에서 특정 타입이 다른 타입에 잘 맞는지 즉, 타입스크립트가 코드를 해석해나가는 과정에서 두 개의 타입이 서로 호환이 되는지를 점검하는것이 타입 호환이라 볼 수 있다.

interface Ironman {
  name: string;
}

class Avengers {
  name: string;
}

let i: Ironman;
i = new Avengers(); // OK, because of structural typing

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

하지만 위와 같은 코드가 타입스크립트에서 정상적으로 동작하는 이유는 자바스크립트의 작동 방식과 관련이 있다.

기본적으로 자바스크립트는 객체 리터럴이나 익명 함수 등을 사용하기 때문에 명시적으로 타입을 지정하는 것보다는 코드의 구조 관점에서 타입을 지정하는 것이 더 잘 어울린다.

구조적 타이핑 예시

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

interface Avengers {
  name: string;
}

let hero: Avengers;

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

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

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

// 위에서 정의한 capt 변수 타입은 { name: string; location: string; }
assemble(capt);

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

interface Developer {
    name: string;
    skill: string;
}

interface Person {
    name: string;
}

let developer: Developer;
let person: Person;

developer = person; // error
person = developer; // ok
let add = function (a: number) {
    //
}

let sum = function(a: number, b: number) {
    //
}

add = sum; // error
sum = add; // ok

반대로 속성이 적은 객체를 속성이 많은 쪽에 대입할 경우 에러가 발생한다.

  • 구조적으로 속성이 많은 경우에만 속성이 작은 쪽으로 호환이 가능하다.
  • 반대로 속성이 많은 쪽에서 적은것을 호환 할 수 없다.
  • 구조적으로 확장 여부를 가늠하면 쉽게 기억할 수 있을듯 싶다.

Soundness란?


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

Enum 타입 호환 주의 사항


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

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

let status: Status = Status.Ready;
status = 100; // no problem
status = Color.Green;  // Error

Class 타입 호환 주의 사항


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

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

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

let a: Hulk;
let s: Captain;

a = s;  // OK
s = a;  // OK

Generics 호환


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

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

x = y;  // OK, because y matches structure of x

위 인터페이스는 일단 속성(member 변수)이 없기 때문에 x와 y는 같은 타입으로 간주된다.

그런데 만약 아래와 같이 인터페이스에 속성이 있어서 제네릭의 타입 인자가 속성에 할당된다면 얘기는 달라진다.

interface NotEmpty<T> {
  data: T;
}
let x: NotEmpty<number>;
let y: NotEmpty<string>;

x = y;  // Error, because x and y are not compatible

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

profile
Move fast & break things
post-custom-banner

0개의 댓글