[TypeScript 독학] #5 타입 추론, 호환, 별칭

안광의·2022년 2월 23일
0

TypeScript 독학

목록 보기
5/12
post-thumbnail

시작하며

오늘은 타입스크립트가 어떤 방식으로 코드를 해석해 나가는 타입 추론과 특정 타입이 다른 타입에 잘 맞는 지를 의미하는 타입 호환, type 키워드를 사용하여 타입을 정의하는 타입 별칭에 대해 정리하면서 타입스크립트의 기본적인 문법 파트를 마치려고 한다. 정말 기초적인 부분만 정리했기 때문에 실제로 프로젝트를 타입스크립트로 변경하는 과정을 거치면서 필요한 부분은 추가적으로 수정해나갈 예정이다.

타입 추론

타입 추론은 타입스크립트가 타입을 따로 지정하지 않더라도 타입을 추론하는 것을 말한다.

let x = 3;

이렇게 x에 타입을 지정하지 않더라도 타입스크립트는 x의 타입을 number로 간주하는데 이 과정은 변수, 속성, 인자의 기본 값, 함수의 반환 값 등을 설정할 때 일어난다.

Best Common Type

let arr = [0, 1, null];

arr의 타입을 추론하기 위해서는 배열의 각 아이템을 살펴봐야하는데, 배열의 각 아이템의 타입은 크게 number와 null로 구분된다. 이 때 Best Common Type 알고리즘으로 다른 타입들과 가장 잘 호환되는 타입을 선정한다.

별도로 타입을 지정하지 않더라도 Best Common Type 알고리즘에 의해 |연산자를 사용한 numbernull타입을 요소로 갖는 배열이 타입으로 설정된 것을 확인할 수 있다.

예제

window.onmousedown = function(mouseEvent) {
  console.log(mouseEvent.button);
  console.log(mouseEvent.onscroll); //error, Property 'onscroll' does not exist on type 'MouseEvent'.
};

타입스크립트는 window.onmousedown에 할당되는 함수의 타입을 추론하기 위해 window.onmousedown 타입을 검사하는데, 타입 검사가 끝나고 나면 함수의 타입이 마우스 이벤트와 연관이 있다고 추론하기 때문에 mouseEvent 인자에 button 속성은 있지만 onscroll 속성은 없다고 결론을 내린다.



타입 호환

타입 호환이란 타입스크립트 코드에서 특정 타입이 다른 타입에 잘 맞는지를 의미한다.

interface Ironman {
  name: string;
}
class Avengers {
  name: string;
}
let i: Ironman;
i = new Avengers(); // OK, because of structural typing

C#이나 Java는 이름을 기준으로 타입을 나누는 Nominal Typing 방식을 사용하기 때문에 Avengers 클래스가 명시적으로 Ironman를 상속받지 않아 에러가 발생하지만 타입스크립트는 Structural Typing이란 구조 관점에서 타입이 호환되는지 여부를 판단하기 때문에 위와 같은 코드에서 에러가 발생하지 않는다. AvengersIronman 모두 name이란 프로퍼티를 가지고 있는 같은 구조이기 때문에 호환되는 타입이라고 판단한 것이다.

타입스크립트 핸드북의 내용만으로는 이해가 가지 않는 부분이 많아서 관련된 포스트를 찾아보았는데

해당 글을 통해서 좀 더 자세하게 이해할 수 있었다.

구조적 타이핑 예시

interface Avengers {
  name: string;
}
let hero: Avengers;
let capt = { name: "Captain", location: "Pangyo" };
hero = capt;

Avengers 인터페이스에서 name 속성을 갖고 있기 때문에 captAvengers 타입에 호환된다.

Class 타입 호환 시 주의 사항

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

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

static member란?
class Hulk {
  static power:number;
  handSize: number;
  constructor(name: string, numHand: number) { }
}
class Captain {
  static leadership: number;
  handSize: number;
  constructor(numHand: number) { }
}
let a: Hulk;
let s: Captain;
a = s;  // OK
s = a;  // OK

static 키워드를 사용하여 클래스 내에 정의한 프로퍼티나 메소드를 말하며 바뀌지 않고 공통적으로 사용될 값에 사용된다.

Generics 타입 호환 시 주의 사항

interface Empty<T> {
}
let x: Empty<number>;
let y: Empty<string>;
x = y;  // OK, because y matches structure of x

제네릭은 제네릭 타입 간의 호환 여부를 판단할 때 타입 인자 <T>가 속성에 할당 되었는지를 기준으로 하는데, 위 인터페이스는 일단 속성(member 변수)이 없기 때문에 xy는 같은 타입으로 간주된다.

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 속성에 할당되었으므로 xy는 서로 다른 타입으로 간주된다.

제네릭의 타입 호환의 주의사항이 핸디북에 작성되어 있어서 정리하기는 하지만 <T>를 설정해놓고 사용하지 않는 경우는 없을 것 같아서 중요하지는 않아 보인다.



타입 별칭 (Type Aliases)

// string 타입을 사용할 때
const name: string = 'capt';
// 타입 별칭을 사용할 때
type MyName = string;
const name: MyName = 'capt';

타입 별칭은 특정 타입이나 인터페이스를 별도의 타입 변수로 정의하여 참조할 수 있는 기능이다. 위와 같이 string, number와 같은 간단한 타입 뿐만 아니라 interface 레벨의 복잡한 타입에도 별칭을 부여할 수 있고 제네릭도 사용할 수 있다.

type Developer = {
  name: string;
  skill: string;
}
type User<T> = {
  name: T
}

type vs interface

타입 별칭과 인터페이스의 가장 큰 차이점은 타입의 확장 가능 / 불가능 여부이다. 인터페이스는 확장이 가능한데 반해 타입 별칭은 확장이 불가능하다. 따라서, 가능한 type 보다는 interface로 선언해서 사용하는 것을 추천한다.



마치며

이번 포스팅을 끝으로 타입스크립의 기초 문법이 되는 파트를 모두 마쳤다. 정말 기본적인 문법만 정리하였기 때문에 추가적으로 수정이 필요할 것이고 많은 레퍼런스들을 보면서 실제로 어떤 식으로 코드가 작성되는지 공부가 필요할 것이다. 다음 파트에서는 모듈, d.ts 파일, 인덱싱 등 타입스크립트 사용에 필요한 여러 파트들과 타입스크립트 관련된 설정이 가능한 tsconfig에 대해서 정리하면서 기존 프로젝트를 타입스크립트로 변경하는데 필요한 내용을 익힐 예정이다.

profile
개발자로 성장하기

0개의 댓글