[Effective Typescript] 타입스크립트의 타입 시스템(1)

이예슬·2022년 11월 13일
0

Effective TypeScript

목록 보기
4/15

분명 해당 글을 2보다 먼저 작성해서 올렸던 것 같은데 왜 사라져있는지 모르겠다...🥲

아이템6. 편집기를 사용하여 타입 시스템 탐색하기

타입스크립트를 설치하면 타임스크립트 컴파일러(tsc)와 타입스크립트 서버(tsserver)를 실행할 수 있다. 여기서 타입스크립트 서버는 언어 서비스를 제공한다.

보통 vscode와 같은 편집기를 통해서 언어서비스를 사용하게 된다.

심벌 위에 마우스를 올리면 타입스크립트가 그 타입을 어떻게 판단하고 있는지 알 수 있다.

언어서비스는 라이브러리와 라이브러리 타입 선언을 탐색할 때도 도움이 되는데 vscode에서도 go to definition을 제공하므로 해당 옵션을 선택해 라이브러리가 타입을 어떻게 선언했는지 확인 할 수 있다.

실제로 매우 유용한 기능 중 하나인데 외부 라이브러리를 사용하다보면 내가 생각한 타입과는 다르게 사용하고 있을 때도 있고 해당 변수가 어떤 타입을 가져야 하는지 헷갈릴 때가 있다. 그럴 때 go to definition을 통해 라이브러리가 타입을 어떻게 사용하고 있는지 확인하면 코드 작성에 도움이 될 때가 많다.

아래는 내가 최근 사용했던 react-quill 라이브러리의 props에 대해 선언한 타입을 나타내고 있다 .

아이템 7. 타입이 값들의 집합이라고 생각하기

타입스크립트가 가지는 타입을 할당 가능한 값들의 집합이라고 생각하면 타입에 대해 이해하기 쉽다.

먼저 집합에서 가장 작은 값은 아무것도 가지지 않은 공집합이다.

타입스크립트에서 공집합은 never이다. never 타입으로 선언된 변수는 타입이 공집합이므로 그 어떤 값도 할당할 수 없다.

그 다음으로 작은 집합은 한 가지 값만 포함하는 타입이다.

이는 타입스크립트에서 유닛(unit)이라고도 불리는 리터럴(literal) 타입이다.

두 개 혹은 세 개로 묶으려면 union 타입을 사용하면 된다. union 타입은 값 집합들의 합집합이다.

type I = 'I' // unit
type You = 'You' //unit
type Us = 'I' | 'You' //union

해당 값의 타입의 집합에 포함되지 않는 값을 선언하면 타입스크립트는 해당 값이 할당 가능하지 않다고 말한다.

const other : Us = 'other' 

집합의 관점에서 타입 체커의 주요 역할은 하나의 집합이 다른 집합의 부분 집합인지 검사하는 것이라고 할 수 있다.

unit과 union들은 어떤 집합의 범위가 한정되어 있으므로 이해하기 쉽지만 실제 타입스크립트로 코드를 작성하게 되면 넓은 범위의 타입을 다루게 되므로 이해하기 어려울 수 있다.

interface Student { 
	name : string; 
} 

interface Age {
	age : number; 
} 

type StudentInfo = Student & Age; 

type에서 & 연산자는 두 타입의 intersection(교집합)을 계산한다. 이 때 타입 연산자는 인터페이스의 속성이 아닌 값의 집합에 적용되기 때문에 추가적인 속성을 가지는 값도 해당 타입에 속한다.

const student1 : StudentInfo = {
	name: Anna; 
	age: 18;
}  // OK

일반적으로 StudentInfo 타입을 선언하는 방법은 extends를 사용하는 것이다.

interface Student { 
	name : string; 
} 

interface StudentInfo extends Student {
	age: number;
	birth: Date;
}

extends는 제너릭 타입에서 한정자로도 쓰이며 이 때는 ~의 부분 집합을 의미하기도 한다.

타입이 집합이라는 것은 동일한 값의 집합을 가지는 두 타입은 같다는 의미가 된다. 그러므로 두 타입이 의미적으로 다르고 우연히 같은 범위를 가진다고 하더라도 같은 타입을 두 번 정의할 이유는 없다.

→ 내가 최근에 고민하던 부분이었는데 책에서 명쾌하게 해결해줬다! 그런데 코드의 가독성 부분에서는 구분해서 적는게 더 편하지 않나…? 하는 생각은 여전히…ㅎㅎ

아이템 8. 타입 공간과 값 공간의 심벌 구분하기

타입스크립트의 심벌(symbol)은 타입 공간이나 값 공간 중의 한 곳에 존재한다.

클래스가 타입으로 쓰일 때는 속성과 메서드가 사용되는 반면 값으로 쓰일 때는 생성자가 생성자가 사용된다.

연산자 중에서도 타입에서 쓰일 때와 값에서 쓰일 때 다른 기능을 하는 것들이 있다.

typeof 연산자는 타입에서 관점에서는 타입스크립트 타입을 반환하고 값의 관점에서는 자바스크립트 런타임의 typeof 연산자가 된다. 자바스크립트 런타임 타입 시스템은 타입스크립트 타입의 정적 타입 시스템보다 훨씬 간단하기 때문에 이 둘은 서로 다르다. (자바스크립트는 string, number, boolean, undefined, object, function 이 6가지만 존재한다)

이처럼 두 공간 사이에서 서로 다른 의미를 가지는 코드 패턴들이 존재한다.

  • this
  • &, |
  • const와 as const
    • as const는 리터럴 또는 리터럴 표현식의 추론된 타입을 바꾼다.
  • extends

이외에도 많은 연산자들이 타입 공간과 값 공간 사이에 다른 의미를 가질 수 있으며 그러므로 코드를 작성할 때는 타입 공간과 값 공간을 혼동하지 않도록 주의해야 한다.

아이템 9. 타입 단언보다는 타입 선언을 사용하기

타입 스크립트에서는 타입을 선언하는 방식은 두 가지가 있다.

interface Student { 
	name: string; 
}

const Tom : Student = { name : 'Tom' }; 
const Kate = { name : 'Kate' } as Student; 

둘 모두 같은 결과를 내놓는 것으로 보이지만 그렇지 않다. 첫번째 Tom의 경우에는 타입 선언을 붙여 값이 선언된 타임임을 명시했고 Kate의 경우에는 as Student라는 타입 단언을 수행한다. 후자의 경우 타입스크립트가 추론한 타입이 있더라도 Person 타입으로 간주한다.

타입 단언은 강제로 타입을 지정했으니 타입 체커에게 오류를 무시하라고 하는 것이다. 이는 속성을 추가할 때도 마찬가지여서 타입 단언문에서는 잉여 속성 체크도 동작하지 않는다. 그러므로 타입 단언이 꼭 필요한 경우가 아니라면 안정성 체크가 되는 타입 선언을 사용하는 것이 좋다.

타입 단언이 꼭 필요한 경우도 존재한다. 타입 단언은 타입 체커가 추론한 타입보다 코드 작성자가 판단하는 타입이 더 정확할 때 의미가 있다. 예를 들어 타입스크립트는 DOM에 접근할 수 없기 때문에 DOM 엘리먼트에 대해서는 타입 단언문을 쓰는 것이 더 유용할 수 있다.

또한 그 값이 null이 아니라고 확인 할 수 있을 때 사용할 수 있다.

const elNull = document.getElementById('foo'); // type is HTMLElement | null 
const el = document.getElementById('foo')!; // type is HTMLElement 

변수의 접미사로 쓰인 !는 그 값이 null이 아니라는 단언문으로 해석된다. HTMLElementHTML Element | null 의 서브타입이므로 위와 같은 타입 단언이 동작할 수 있다.


<이펙티브 타입스크립트> Dan Vanderkam, 프로그래밍 인사이트 (2021)

profile
꾸준히 열심히!

0개의 댓글