interface User {
name: string;
height: number;
}
interface Pet {
name: string;
species?: string;
}
const user: User = { name: '안희종', age: 176 };
const puppy: Pet = { name: '해피' };
아래 코드 실행하면 어떤 결과가 나올까?
``` const pet2: Pet = user; // ?? const user2: User = pet; // ?? ```
두 가지 기준
User는 Pet에 할당 가능한가?
Pet
의 필수 멤버는name
하나다. 같은 이름의 멤버가User
에도 존재한다.Pet
의name
멤버 타입은string
이다(type M = string
).User
의name
멤버 타입 또한 그렇다(type M' = string
).
둘은 같은 타입이므로 당연히 서로 할당 가능하다.
두 조건이 모두 만족되므로, User는 Pet에 할당 가능하다. 이 때 두 가지 눈여겨 볼 점이 있다.
Pet
에는User
의height: number
속성이 존재하지 않지만 이는 무관하다. 할당 가능성을 따질 때엔 할당을 받는 쪽의 타입만이 중요하다.- 선택 속성으로 정의된
species?: string
은 없어도 무방하다.
Pet은 User에 할당 가능한가?
User
의 필수 멤버는name
과height
둘이다. 이 때,Pet
타입엔height
멤버가 존재하지 않는다.
따라서 두 번째 조건은 볼 것도 없이, Pet
은 User
에 할당 불가능하다. const user2: User = pet
과 같은 시도는 타입 에러를 발생시킨다.
구조적 타입 시스템
이렇듯, 타입스크립트에서는 두 타입의 구조(structure)만을 비교하여 호환성을 결정한다. 어떤 타입이 다른 타입이 갖는 멤버를 전부 가지고 있다면 그걸로 충분하며, 두 타입이 호환되는 타입이라는 명시적인 표시는 필요하지 않다. 이렇게 동작하는 타입 시스템을 구조적 타입 시스템(structural type system)이라 부른다
객체 리터럴과 과잉 속성 검사
다음 코드를 보자.
interface Color {
R: number;
G: number;
B: number;
}
const white: Color = {
R: 255,
G: 255,
B: 255,
A: 1
};
변수 `white`는 `Color` 타입을 갖는다. 그리고 `white`에 할당하려는 객체는 `R`, `G`, `B` 세 멤버를 모두 갖고 있고, 세 멤버의 모두 `number` 타입다. 따라서 위에서 살펴본 구조적 타입 검사에 의하면 이 할당에는 아무런 문제가 없어야 한다.
하지만 실제로 위의 할당은 에러가 발생한다. 에러 메시지는 다음과 같다
error TS2322: Type '{ R: number; G: number; B: number; A: number; }' is not assignable to type 'Color'.
Object literal may only specify known properties, and 'A' does not exist in type 'Color'.
객체 리터럴은 알려진 속성(known property)만을 가질 수 있는데, Color
타입에는 A
속성이 존재하지 않으므로 할당이 불가능하다는 메시지이다. 하지만 분명 할당을 받는 쪽의 타입만이 중요하고, 추가적인 멤버를 갖는건 상관 없다고 하지 않았나? 이게 어찌 된 일일까?
이러한 현상이 발생하는 이유는 우리가 할당하는 값이 변수나 표현식이 아닌 객체 리터럴
이기 때문이다. 객체 리터럴을 할당하는 경우에는 그 리터럴이 알려지지 않은 속성(unknown property), 즉 할당 받는 타입에 존재하지 않는 속성을 포함한다면 타입 에러가 발생한다.
실제로 위의 코드를 아래와 같이 객체 리터럴이 아닌 변수를 할당하도록 바꾸면 에러는 사라진다.
interface Color {
R: number;
G: number;
B: number;
}
const someColor = {
R: 255,
G: 255,
B: 255,
A: 1
};
const white: Color = someColor;
과잉 속성 검사가 존재하는 이유
과잉 속성 검사는 프로그래머의 실수를 막기 위해 존재한다. 어떤 타입의 값에 객체 리터럴을 직접 할당하는 경우, 만약 해당 타입에 정의되지 않은 멤버는 오타 등의 실수로 인해 존재할 확률이 높다고 가정하는 것이다. 공식 문서에서 빌려온 아래 예제를 보자.
interface SquareConfig {
width?: number;
color?: string;
}
const squareConfig: SquareConfig = {
width: 100,
colour: red
};
구조적 타입 시스템의 원칙에 따르면 위 할당에는 문제가 없다.
하지만 할당 시점에 딱 한 번만 사용될 객체 리터럴에 colour
라는 알려지지 않은 속성이 존재한다면, color
를 타이핑하려다 오타가 난 경우가 대부분일 것이다.
과잉 속성 검사를 시행해서 이런 흔하게 예상되는 오류를 컴파일러가 잡아 줄 수 있다
'TypeScript' 카테고리의 다른 글
타입의 호환성 - 클래스 (0) | 2019.10.30 |
---|---|
타입의 호환성 - 함수 타입 (0) | 2019.10.30 |
타입의 호환성 - 기본 타입 (0) | 2019.10.30 |
인터페이스와 클래스의 관계 (0) | 2019.10.30 |
추상 클래스 (0) | 2019.10.30 |