타입스크립트의 타입 시스템 [1]

DOHEE·2022년 11월 2일
0

EffectiveTypescript

목록 보기
2/11

타입스크립트의 가장 중요한 역할 중 하나는 타입 시스템이다.

타입을 지정함으로써 코드의 안정성이 보장되기 때문이다.

[1] 편집기를 사용하여 타입 시스템 탐색하기

타입스크립트를 설치하면, tsc(타입스크립트 컴파일러)tsserver(타입스크립트 서버)를 실행할 수 있다.

tsserver는 '언어 서비스'를 제공하는데 편집기뿐만 아니라 tsserver에도 언어 서비스를 설정해두면 좋다.

편집기를 이용하는 경우 각 변수에 대한 지정된 타입을 확인할 수 있다. typescript는 따로 타입을 지정해주지 않은 경우 알아서 추론해준다. 보통 변수에 마우스 오버를 하면 타입을 확인할 수 있다.

먼저 타입을 지정하기 보다는 타입스크립트가 추론한 타입을 확인하고 본인이 의도한 타입과 다를 경우 추가하는 것이 좋다.

언어 서비스는 라이브러리와 라이브러리 타입 선언을 탐색할 때 도움이 된다. 편집기는 'Go to Definition(정의로 이동)' 옵션을 제공하므로 이를 통해 해당 라이브러리가 정의된 지점으로 이동일 가능하다.

vscode에서는 'ctrl+클릭'으로 이동이 가능한데 vscode 밖에 안 써봐서 다른 편집기는 잘 모르겠다.

정의로 이동 기능을 따라 가다 보면 해당 라이브러리의 구조와 타입을 이해할 수 있다.

[2] 타입이 값들의 집합이라고 생각하기

number 타입은 어떻게 구성되어 있을까?

42와 37.25는 모두 number로 인식되어야 한다.

만약 타입을 집합으로 생각한다면 number에는 무한대의 숫자가 들어있는 집합일 것이다.

그렇다면 공집합을 의미하는 타입은 뭘까? never이다.

never 타입에는 그 어떤 값도 들어올 수 없다.

그 다음으로 작은 타입은 unit 타입으로도 불리는 literal 타입이다. literal 타입은 아래 예시와 같이 그 값 딱 하나만을 의미하는 타입이다.

type A = "A";
type dohee = "dohee";

그렇다면 DoheeA라는 변수에 A 또는 dohee를 넣고 싶다면 어떤 타입을 써야 할까?

물론 string을 쓰면 해결된다. 하지만 딱 두 개만 들어올 수 있게 만들고 싶다면 어떻게 해야 할까? 아래와 같이 Union 타입을 사용하면 된다.

type DoheeA = "dohee" | "A";

위의 예시를 집합으로 생각해보면 DoheeA라는 집합 안에는 dohee와 A만 들어있는 것이다. 따라서 이 집합 안에 B가 있냐고 묻는다면 당연히 없다고 대답한다.

하지만 literal에 Intersection 타입을 적용하면 never와 동일하다. 왜냐하면 dohee이면서 A인 것은 존재하지 않기 때문이다.

이런 의미에서 단순한 값이 아닌 인터페이스는 조금 이해가 어려울 수 있다.

name을 key로 가지는 Person과 birth를 key로 가지는 LifeSpan이 있다고 가정해보자.

name이 있으면 Person이 될 수 있고, birth가 있으면 LifeSpan이 될 수 있다. 따라서 name과 birth를 둘 다 갖고 있는 PersonLifeSpan은 둘의 교집합이 된다.

반대로 둘의 합집합에는 그 어떠한 key도 있을 수 없다.

이처럼 타입을 집합으로 이해한다면 extends에 대해서 더 잘 이해할 수 있다. A extends B는 A의 요소들이 B에 할당 가능하다는 의미로 A가 B의 부분집합이라는 것을 의미한다.

[3] 타입 공간과 값 공간의 심벌 구분하기

타입스크립트에서 타입의 중요성이 큰 만큼 어떤 것이 타입을 의미하고 어떤 것이 값을 의미하는지 구분하는 것이 꽤나 중요하다.

이름이 같더라도 속하는 공간에 따라 다른 것을 나타낼 수 있어 혼란을 야기한다.

구분하는 방법 중 하나는 타입스크립트 플레이그라운드(Typescript Playground)를 활용하는 것이다. 이 웹사이트에서는 Typescript 코드를 실시간으로 Javascript 코드로 변환된 결과물을 보여준다. 타입은 변환 과정에서 사라지므로 사라지면 타입, 남아있으면 변수라고 볼 수 있다.

class와 enum은 상황에 따라 타입과 값 두 가지 모두 가능하다.

경험 상 interface는 타입으로만 사용할 수 있다.
이를 구분하는 방법은 책에 아주 자세하게 나오니 책을 참고하길 바란다.

가장 인상깊었던 부분은 destructing 부분인데 개인적으로 함수에 destructing 문법을 애용하는 편이다. typescript로 넘어오면서 구조 분해 할당을 할 때, 타입을 설정할 수 있는 방법이 없을까 고민했다.

typescript의 구조 분해 할당은 다음과 같은 방식을 통해 가능하다.

const newFunc = (
	{name, age, dessert}:{name: string, age: number, dessert: string}
) : void => { 
	console.log(`내 이름은 ${name}! ${dessert}를 사랑하지!`);
    console.log(`그렇게 내 나이 ${age}에 생을 마감하였다...`);
}

[4] 타입 단언보다는 타입 선언을 사용하기

interface Person { name: string };

const dohee: Person = { name: "dohee" }; // 타입 선언
const heongyu = { name: "heongyu" } as Person; // 타입 단언

타입 선언은 그 값이 선언된 타입임을 명시하는 것이고 타입 단언은 타입스크립트의 추론을 무시하고 그 타입이 옳다고 단언하는 것이다.

타입 선언의 경우 타입체커가 옳은 타입인지 확인해주는 반면 타입 단언에는 타입을 확인해주지 않는다. 따라서 타입 단언을 할 경우 안정성이 저하된다.

화살표 함수를 사용할 때에 제대로 반환값의 타입이 추론되지 않는 경우가 있다. 이런 경우에 무심코 단언문을 쓰게 되는데 변수에 타입을 지정해서 return해주거나 반환값의 타입을 명시하여 해결할 수 있다.

마지막에 사용하는 !는 null이 아님을 단언하는 의미라고 할 수 있다. 이 또한 단언문이기 때문에 null이 올 수 있는 상황에도 타입체커가 제대로 확인해주지 않는다.

물론 타입스크립트의 타입체커보다 타입에 대해서 더 잘 안다면 단언문을 사용할 수 있지만, 가급적이면 타입 선언문을 사용할 것을 권장한다.

profile
안녕하세요 : ) 천천히라도 꾸준히 성장하고 싶은 개발자 DOHEE 입니다! ( 프로필 이미지 출처 : https://unsplash.com/photos/_FoHMYYlatI )

0개의 댓글