본문 바로가기
개인공부/TypeScript

TypeScript Type에 대한 이해

by 왕큰새 2022. 3. 6.
728x90

TypeScript의 Type은 Java와 상당히 다르다. 몇 가지 차이점을 살펴보자.

이름으로 구체화된 타입 시스템 (Nominal Reified Type Systems)

Java

주어진 값과 객체는 'null', 원시 타입, 정의된 클래스 타입 중 하나의 타입을 가진다.

런타임 시점에 정확한 타입을 묻는다. 해당 시점에 GetType(), getClass()를 호출할 수 있다.

명시적 상속관계나 공통적으로 구현된 인터페이스가 없는 이상 클래스가 유사한 형태를 가졌다해도 대체하여 사용 불가

타입은 구조가 아닌 선언을 통해 연관 지어진다.

 

TypeScript

타입은 어떤 형태로도 런타임에서 존재하지 않는다

유사한 형태를 가질 경우 대체하여 사용 가능

타입은 구조를 통해 연관 지어진다.

 

 

집합으로서의 타입 (Types as Sets)

Java에서 런타임 타입과 해당 컴파일 타임 선언 사이의 일대일 대응관계는 중요하다.

 

TypeScript에서의 타입은 공통의 무언가를 공유하는 값의 집합

그러므로, 특정한 값은 수많은 집합에 속할 수 있다.

 

Java에서는 string과 int 둘 다 가능한 타입이 존재하지 않기 대문에 이 값을 인자로 전달하는 것은 이상하다.

TypeScript에서는 string 집합 또는 number 집합에 속할 수 있는 값은 그 지합들의 유니언: string | number에 속한다.

 

TypeScript에서는 Type을 집합으로 생각하자!

 


삭제된 구조적 타입 (Erased Structural Types)

TypeScript에서 객체는 단일 타입이 아니다.

인터페이스를 만족하는 객체를 생성할 때, 둘 사이의 선언적 관계가 없더라도,

해당 인터페이스가 예상되는 곳에 해당 객체를 사용할 수 있다.

 

interface Pointlike {
  x: number;
  y: number;
}
interface Named {
  name: string;
}

function printPoint(point: Pointlike) {
  console.log("x = " + point.x + ", y = " + point.y);
}

function printName(x: Named) {
  console.log("Hello, " + x.name);
}

const obj = {
  x: 0,
  y: 0,
  name: "Origin",
};

printPoint(obj);
printName(obj);

TypeScript의 타입 시스템은 명목이 아닌 구조적이다.

obj는 숫자인 x , y 프로퍼티를 가지고 있으므로, pointlike로써 사용될 수 있다.

타입 간의 관계는 특정 관계로 선언되었는지가 아닌, 포함된 프로퍼티에 의해 결정된다.

 

집합으로서의 타입 개념으로 보면, obj를 Pointlike 값 집합이나 Named 값 집합의 멤버로 간주할 수 있다.

 


빈 타입 (Empty Types)

class Empty {}

function fn(arg: Empty) {
  // 무엇인가를 하나요?
}

// 오류는 없지만, '빈' 타입은 아니지 않나요?
fn({ k: 10 });

TypeScript는 주어진 인수가 유효한 Empty인지 확인하여 fn의 호출이 유효한지 검사한다. 

{ k : 10 } 과 class Empty의 구조를 확인하여 유효성을 검사한다.

 

최종적으로 명목적인 객체지향프로그래밍 언어와 매우 비슷하게 사용된다. 파생 클래스와 파생 클래스의 기본 사이의 자연스러운 하위 타입 관계가 파괴되기 때문에, 하위 클래스는 삭제할 수 없다. 구조적 타입 시스템은 호환 가능한 유형의 속성을 갖는 측면에서 하위 타입을 설명하므로 위의 관계를 암시적으로 구별한다.

 

결국 빈 타입이 실제 빈 타입이 아닌 것이다. 

빈 객체가 아니여도, nullish하지 않다면 아무 값이나 다 들어갈 수 있다는 뜻이다.

 

그렇다면, '빈 객체는' 어떻게 정의하는 가? - never 타입을 활용하자


동일한 타입 (Identical Types)

class Car {
  drive() {
    // hit the gas
  }
}
class Golfer {
  drive() {
    // hit the ball far
  }
}

// No error?
let w: Car = new Golfer();

오류가 아닌 이유는 클래스의 구조가 동일하기 때문이다.  

 

 


 

반영(Reflection)

OOP 프로그래머는 제네릭(<T>)을 포함한 모든 유형의 값을 다룰 수 있는 것에 익숙하다.

TypeScript은 타입 시스템이 완벽히 지워졌음으로

제네릭 타입 인자의 인스턴스화 같은 정보는 런타임에 사용할 수 없다.

 

변수의 타입을 체크, 객체의 구조를 탐색하는 과정을 반영(Reflection)이라 한다.

반영을 매개변수에 사용하여 매개변수의 타입과 속성을 알 수 있다. - typeof, instanceof 이용