📢 어렵고 정석적인 개념 설명보다는 저같은 초보자도 이해하기 쉽게 정리하는 것을 원칙으로 포스팅하고 있습니다. 😄
타입의 호환성
타입스크립트의 타입들은 각자 타입마다 호환되는 타입이 있고 호환되지 않는 타입이 존재한다.
이전 글에서 unknown 타입에 대해 설명할 때, 값으로서는 사용하지 못하는 타입이라고 했었다.
(unknown 타입에 number 타입은 들어가지만, number 타입에 unknown 타입은 들어가지 못한다.)
이게 바로 타입의 호환성이며, 타입마다 어떤 타입은 값으로 사용이 가능하지만 어떤 타입은 불가능한 걸 뜻한다.
타입에는 슈퍼 타입과 서브 타입이 있다. (슈퍼 타입은 부모 타입, 서브 타입은 자식 타입이라고 봐도 무방하다.)
슈퍼 타입은 서브 타입을 반환할 수 있지만, 서브 타입은 슈퍼 타입을 반환하지 못한다.
예를 들어, void 타입과 undefined 타입을 놓고 비교했을 때 void 타입은 undefined 타입의 슈퍼 타입이다.
따라서, void 타입은 undefined 타입을 반환할 수 있다. (값으로써 사용할 수 있다.) 하지만, undefined 타입은 void 타입의 서브 타입이기 때문에 void 타입을 반환할 수 없다.
let unknownVar: unknown;
let num: number = 10;
unknownVar = num; // 정상
num = unknownVar; // Error! => unknown 타입이 number 타입보다 슈퍼 타입임
let voidVar: void;
let undefinedVar: undefined = undefined;
voidVar = undefinedVar; // 정상
undefinedVar = voidVar; // Error! => void 타입이 undefined 타입보다 슈퍼 타입임
설명만 있으면 보기 힘들 수도 있으므로 코드로 표현해 봤다. 코드 설명은 위의 내용과 같다.
타입 간의 슈퍼 타입과 서브 타입을 대략적으로 정리해보자면, unknown 타입이 최상위 타입이고 never 타입이 최하위 타입이다. 중간에는 number, string, boolean, object 등이 있으며 각 기본 타입의 리터럴 타입은 서브 타입이 된다.
객체 타입 간의 호환성
type Type1 = {
name: string;
color: string;
phone: number;
}
type Type2 = {
name: string;
color: string;
}
// Type1은 Type2보다 프로퍼티가 더 많다.
// 객체에서 프로퍼티가 더 많으면 슈퍼 타입일까?
let user1: Type1 = {
name: 'Kim',
color: 'blue',
phone: 1234
}
let user2: Type2 = {
name: 'Lee',
color: 'green'
}
user2 = user1; // 정상
user1 = user2; // Error! => Type2가 Type1의 슈퍼 타입이기 때문
타입 객체에서는 어떨까? 타입 객체에서 슈퍼 타입과 서브 타입을 결정하는 요소는 프로퍼티의 개수이다.
일반적인 생각으로는 "프로퍼티의 개수가 더 많은 타입이 당연히 슈퍼 타입 아닐까?" 하는 생각이 들 수도 있지만, 아쉽게도 프로퍼티의 개수가 더 적은 타입이 슈퍼 타입이다.
위 예시 코드의 Type2를 보면, name, color 프로퍼티가 존재한다. 그럼, Type2은 "name, color 프로퍼티를 가지고 있는 애들은 모두 Type2 타입이야"라고 보는 것이다. 예를 들어, Type2가 Type1를 보면 "어? Type1의 프로퍼티에는 name과 color 프로퍼티가 존재하네? 그럼 너도 내 타입이야"라고 생각한다. 따라서, Type2는 Type1의 슈퍼 타입이 될 수 있다.
단순히 프로퍼티 개수가 많고 적음으로 슈퍼 타입을 따지는 게 아니라 프로퍼티가 적기 때문에 자연스레 프로퍼티가 많은 객체에 포함될 수 있는 것(이게 곧 슈퍼 타입) 이라고 이해해야 한다.
type User = {
id: number;
name: string;
}
type Guest = {
id: number;
name: string;
role: string;
}
let user: User;
let guest: Guest = {
id: 2,
name: 'Lee',
role: 'guest'
}
user = guest; // 이것과
user = { // 이것은 사실상 똑같은 식 아닌가?
id: 2,
name: 'Lee',
role: 'guest'
}
// 그럼 사실상 이것도 맞는 거 아니야?
let user2: User = {
id: 2,
name: 'Lee',
role: 'guest' // 초과 프로퍼티 Error!
}
슈퍼 타입 객체에 서브 타입 객체를 할당시킬 수 있다고 했다. 위의 예시 코드에서 user는 슈퍼 타입, guest는 서브 타입이다. 따라서, user = guest도 정상 작동된다. 그럼 사실상 User 타입에 Guest 타입이 들어간 건데, 그렇게 따지면 선언할 때부터 User 타입 프로퍼티에 Guest 타입 프로퍼티를 추가해 볼 수도 있지 않을까? (... 싶을 것이다.)
결론은 당연히 동작하지 않는다. 객체를 선언할 때는 '초과 프로퍼티 검사'라는 게 우선적으로 실행되기 때문이다. 하물며 슈퍼 타입과 서브 타입은 두 개의 객체가 서로 변수나 값으로서 할당될 때 비교하면서 나오는 개념이지 그냥 변수로서 선언할 때는 타입스크립트의 기본 원리인 타입 프로퍼티에 맞춰서 동작하기 때문에 위 코드는 전혀 이상하다고 볼 수 없다.
1. 타입끼리는 서로 호환된다. (최상위 타입은 unknown, 최하위 타입은 never)
2. A와 B라는 타입이 있을 때, A ⊃ B이면 A가 슈퍼 타입이 되고 B가 서브 타입이 된다.
3. 슈퍼 타입 변수에는 서브 타입의 값이 할당될 수 있지만, 서브 타입 변수에는 슈퍼 타입의 값이 할당될 수 없다.
4. 객체 타입도 호환되는데, 객체의 슈퍼 타입과 서브 타입의 결정 요소는 프로퍼티의 개수이다.
5. 프로퍼티가 더 적은 객체가 슈퍼 타입이며, 프로퍼티를 모두 포함하고 있는 점에서 상위 요소라고 본다.
'TypeScript' 카테고리의 다른 글
[TypeScript] 타입스크립트 함수 타입 정의 방법 (0) | 2023.07.25 |
---|---|
[TypeScript] 타입 단언으로 변수에 타입 부여해주기 (vs 타입 선언) (0) | 2023.07.23 |
[TypeScript] 타입 추론 : 타입 넓히기와 타입 좁히기 (0) | 2023.07.20 |
[TypeScript] 타입스크립트 기초 : 변수 뒤에 타입 달기 (0) | 2023.07.15 |
[TypeScript] 타입스크립트 설치 및 컴파일러 세팅하기 (0) | 2023.07.12 |