타입스크립트 (4) 이해하기

김태완·2024년 11월 19일
post-thumbnail

최근 React로 작업한 프로젝트에 TypeScript를 적용해보았다. 그런데 왜 적용하며, 어디까지 타입을 정의해야하는지 등 여러 의문이 생겼었다. 또한, 타입 오류를 해결하는데 급급해서 코드가 지저분해지는 경험을 했다. 그저 문법만 배워서 프로젝트에 적용하면서 생긴 문제인 것 같다. 이번 과정을 통해 좀 더 명확한 이해가 된다면 훨씬 효율적으로 적용할 수 있을 것 같다.

타입스크립트 이해하기?

  • 타입스크립트를 이해한다는 말은 타입스크립트가 어떤 기준으로 타입을 정의하고, 어떤 기준으로 타입들간의 관계를 정의하고, 어떤 기준으로 타입스크립트 코드의 오류를 검사 하는지 그 원리와 동작 방식을 이해하자는 것이다.
  • 실제로 타입스크립트를 적용해보니, 그저 문법만 외워서 쓴다는 것은 복잡해질수록 적용하기 더 어려웠다.

타입은 집합이다?

  • 타입스크립트의 '타입'은 사실 여러개의 값을 포함하는 '집합'이다. 집합은 동일한 속성을 갖는 여러개의 요소들을 하나의 그룹으로 묶은 단위를 말한다. 따라서 다음 그림처럼 여러개의 숫자 값들을 묶어 놓은 집합을 타입스크립트에서는 number 타입이라고 부른다.
  • 그렇다면 오직 하나의 값만 포함하는 타입인 Number Literal 타입은 어떤 집합일까? 예를 들어 20 이라는 Number Literal 타입이 존재한다면 이 타입은 다음 그림과 같이 딱 하나의 값만 포함하는 아주 작은 집합이라고 볼 수 있다.
  • 그리고 이 20 이라는 타입에 속하는 요소인 숫자 20은 Number 타입이라는 거대한 집합에도 속하는 값입니다. 20도 결국 숫자이기 때문입니다. 그러므로 결국 모든 Number Literal 타입은 Number 타입이라는 거대한 집합에 포함되는 부분 집합으로 볼 수 있다.
  • 사실 타입스크립트의 모든 타입들은 집합으로써 서로 포함하고 또 포함되는 이런 관계를 갖는다. 그리고 이런 관계에서 Number 타입처럼 다른 타입을 포함하는 타입을 슈퍼 타입(부모 타입)이라고 부른다. 반대는 서브 타입(자식 타입)이라고 한다.

타입 호환성?

  • 타입 호환성이란 예를 들어 A와 B 두개의 타입이 존재할 때 A 타입의 값을 B 타입으로 취급해도 괜찮은지 판단하는 것을 의미한다.
  • 그래서 만약 A 타입의 값이 B 타입의 값으로 취급 되어도 괜찮다면 호환된다고 하고 안된다면 호환되지 않는다고 한다.
  • 그래서 위와 같은 개념을 적용해서 아래의 코드를 살펴본다면
    • 이 코드는 전혀 문제가 되지 않는다. 변수 num1의 타입이 더 큰 타입(Number 타입)이기 때문이다.
  • 그런데 반대의 경우에는 성립되지 않는다.
    • 이는 Number 타입의 값을 Number Literal 타입의 변수에 할당하는 것이고, Number 타입의 값을 Number Literal 타입의 값으로 취급 하겠다는 것이다.
    • 이렇게 더 큰 타입의 값을 더 작은 타입의 값으로 취급하는 것은 안된다.
    • 그런데 왜 안되는걸까? 집합으로 생각해보면 이해하기 쉽다.
    • 변수 num1Number 타입이므로 10외에도 999나 -123123 같은 다양한 숫자 값을 담을 수 있다.그러나 변수 num2Number Literal 타입이므로 10외의 다른 값은 절대 담을 수 없다.
    • 그러므로 num2num1의 값을 저장하게 되면 문제가 발생하는 것이다.
  • 타입스크립트에서는 이렇게 슈퍼타입의 값을 서브타입의 값으로 취급하는것을 허용하지 않는다. 반대로는 허용한다.
  • 그리고 서브 타입의 값을 슈퍼 타입의 값으로 취급하는 것은 업 캐스팅 이라고 부르고 반대는 다운캐스팅이라고 부른다.
  • 따라서 쉽게 정리하면 업캐스팅은 모든 상황에 가능하지만 다운 캐스팅은 대부분의 상황에 불가능하다고 할 수 있다.

타입 계층도 살펴보기

unknown 타입 (전체 집합)

  • unknown 타입은 타입 계층도의 최상단에 위치한다.
  • 즉, unknown 타입은 모든 타입의 슈퍼타입이라는 뜻 입니다. 그러므로 모든 타입은 unknown 타입의 부분집합입니다.

  • unknown 타입 변수에는 모든 타입의 값을 할당할 수 있다.
  • 즉, 모든 타입은 unknown 타입으로 업 캐스트 할 수 있다는 뜻이다.
  • 그렇지만 다운캐스트는 any를 제외한 어떤 타입의 변수에도 허용되지 않는다!

never 타입 (공집합)

  • never 타입은 타입 계층도에서 가장 아래에 위치한다.
  • never 타입은 불가능, 모순을 의미하는 타입이다.
  • never는 공집합을 뜻하는 타입이다. 공집합은 아무것도 포함하지 않는 집합이라는 뜻이다.
  • 따라서 never 타입에 해당하는 값은 말 그대로 아무것도 없다.
  • 공집합은 모든 집합의 부분 집합이다. 그러므로 never 타입은 모든 타입의 서브 타입이다.
  • 따라서 never 타입은 모든 타입으로 업캐스팅 할 수 있다.
  • 반면 그 어떤 타입도 never 타입으로 다운 캐스팅 할 수 없다.

void 타입

  • void 타입은 아무것도 반환하지 않는 함수의 반환값 타입으로 주로 사용된다.
  • 타입 계층도에서 void 타입을 찾아보면 void 타입은 undefined 타입의 슈퍼타입이다.
  • 따라서 반환값을 void로 선언한 함수에서 undefined을 반환해도 오류가 발생하지 않는다. undefined 타입은 void 타입의 서브 타입이므로 업캐스팅이 가능하기 때문이다.
  • void 타입의 서브타입은 undefined 타입과 never 타입 밖에 없다.
  • 따라서 void 타입에는 undefined, never 이외에 다른 타입의 값을 할당할 수 없다.

any 타입

  • any 타입은 타입 계층도를 완전히 무시한다.
  • any는 뭐든지 예외이다. 모든 타입의 슈퍼타입이 될 수도 있고, 모든 타입의 서브 타입이 될 수도 있다.
  • 단, never 타입으로는 다운 캐스트 할 수 없다.

객체 타입의 호환성??

  • 객체 타입간의 호환성도 동일한 기준으로 판단한다.
  • 모든 객체 타입은 각각 다른 객체 타입들과 슈퍼-서브 타입 관계를 갖는다.
  • 따라서 업 캐스팅은 허용하고 다운 캐스팅은 허용하지 않는다.
  • Animal 타입의 변수 animal에 Dog 타입의 변수 dog를 할당하는 것은 가능하다. 그러나 반대로 dog 변수에 animal 변수의 값을 할당하는 것은 불가능하다. Animal 타입이 Dog 타입의 슈퍼타입이기 때문이다.
  • 그런데 Animal 타입이 왜 Dog 타입의 슈퍼 타입일까? 언뜻 보면 Dog 타입이 더 많은 프로퍼티를 정의하고 있어 슈퍼타입처럼 보일 수 있지만 그 반대이다.
  • 타입스크립트는 프로퍼티를 기준으로 타입을 정의하는 구조적 타입 시스템을 따른다. 따라서 Animal 타입은 name과 color 프로퍼티를 갖는 모든 객체들을 포함하는 집합으로 볼 수 있고, Dog 타입은 name과 color 거기에다 추가로 breed 프로퍼티를 갖는 모든 객체를 포함하는 집합으로 볼 수 있다.
  • 그러므로 어떤 객체가 Dog 타입에 포함된다면 무조건 Animal 타입에도 포함된다. 그러나 반대로 Animal 타입에 포함되는 모든 객체가 Dog 타입에 포함되는것은 아니다. 따라서 결국 Animal은 Dog의 슈퍼타입입니다.
    - 객체의 조건이 많을수록 슈퍼 타입 같겠지만, 그 반대다.

초과 프로퍼티 검사?

  • Book 타입으로 정의된 변수에 ProgrammingBook 타입으로 보이는 초기값을 설정했다. 그 결과 오류가 발생한다. 업캐스팅에 해당되는데 왜 오류가 발생하는 걸까?
  • 결론부터 말하면 이것은 ‘초과 프로퍼티 검사’가 발동해서 그렇다.
  • 초과 프로퍼티 검사란 변수를 객체 리터럴로 초기화 할 때 발동하는 타입스크립트의 특수한 기능이다. 이 기능은 타입에 정의된 프로퍼티 외의 다른 초과된 프로퍼티를 갖는 객체를 변수에 할당할 수 없도록 막는다.
  • 따라서 위 코드는 Book 타입에 정의되지 않은 skill 프로퍼티를 갖는 객체를 할당하려고 했으므로 초과 프로퍼티 검사가 실패해서 오류가 발생하고 있는 것이다.
  • 이런 초과 프로퍼티 검사는 단순히 변수를 초기화 할 때 객체 리터럴을 사용하지만 않으면 발생하지 않는다.
  • 초과 프로퍼티 검사는 함수의 매개변수에도 동일하게 발생한다.
  • 함수의 매개변수에 인수로 값을 전달하는 과정도 변수를 초기화 하는 과정과 동일하다. 따라서 초과 프로퍼티 검사가 발동하게 된다.
  • 이때에도 역시 검사를 피하고 싶다면 변수에 미리 값을 담아둔 다음 변수값을 인수로 전달하면 된다.

이미지 출처: <한 입 크기로 잘라먹는 타입스크립트>
https://www.inflearn.com/course/%ED%95%9C%EC%9E%85-%ED%81%AC%EA%B8%B0-%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8?inst=68d752f7&utm_source=instructor&utm_medium=referral&utm_campaign=inflearn_%ED%8A%B8%EB%9E%98%ED%94%BD_promotion-link

profile
중고

0개의 댓글