타입스크립트에서 구조적 타이핑으로 인해 유니온 타입의 논리가 조금 이상한데 이유가 뭘까요? 다음 예시를 보면 오류를 내지 않고 있어요. 그래서 유사한 구조의 타입들을 구분하기 위해서는 Discriminated Unions 를 사용하라고 하는데요. 애초에 유니온을 만들때 부터 정확한 논리구조로 만들 수는 없는 것이었을까요? 구조적 타이핑, 덕 타아핑의 한계로 인해 어쩔 수 없는 것인가요?
다음의 예시에서 data 는 a, b, c, d 속성을 모두 가지고 있음에도 에러를 내지 않는다.
type A = {
a: string;
b: string;
}
type B = {
a: string;
b: string;
c: number;
}
type C = {
a: string;
b: string;
d: () => void;
}
type ABC = A | B | C;
const data: ABC[] = [
{
a: 'a',
b: 'b',
c: 123,
d: () => {}
},
];
타입스크립트에서 발생하는 문제는 **구조적 타이핑(Structural Typing)**과 관련이 있으며, 이로 인해 유니온 타입의 논리가 기대한 대로 작동하지 않을 수 있습니다. 이 문제를 이해하기 위해서는 구조적 타이핑의 기본 개념과 덕 타이핑(Duck Typing)의 특성을 먼저 살펴볼 필요가 있습니다.
1. 구조적 타이핑과 덕 타이핑
타입스크립트는 구조적 타이핑(Structural Typing) 시스템을 사용합니다. 이는 객체의 타입이 해당 객체의 구조에 의해 결정된다는 것을 의미합니다. 객체가 특정 인터페이스나 타입을 구현하고 있는지를 판단할 때, 타입스크립트는 객체가 그 구조를 가지고 있는지만 확인합니다. 이와 유사한 개념이 덕 타이핑인데, "오리가 걷고, 수영하고, 꽥꽥거린다면 그것은 오리다"라는 논리로 설명됩니다. 즉, 객체가 특정 구조나 메서드를 가지고 있으면 그 객체는 해당 타입으로 간주됩니다.
2. 유니온 타입의 동작
유니온 타입(A | B | C)은 여러 타입 중 하나를 가질 수 있는 타입입니다. 타입스크립트에서 유니온 타입의 요소가 되는 각각의 타입이 어떤 속성을 가지는지를 기준으로 객체의 타입을 확인합니다.
타입스크립트에서는 ABC라는 유니온 타입이 정의되었을 때, ABC 타입의 객체는 A, B, C 타입 중 하나로 간주될 수 있습니다. 이 때, 객체가 A 또는 B 또는 C 타입의 구조를 만족하기만 하면 그 객체는 유효한 타입으로 인정됩니다.
3. 문제의 예시 분석
type A = {
a: string;
b: string;
}
type B = {
a: string;
b: string;
c: number;
}
type C = {
a: string;
b: string;
d: () => void;
}
type ABC = A | B | C;
const data: ABC[] = [
{
a: 'a',
b: 'b',
c: 123,
d: () => {}
},
];
위 예시에서, data 배열에 포함된 객체는 a, b, c, d 네 가지 속성을 모두 가지고 있습니다. 그런데도 타입스크립트는 이 객체를 ABC 유니온 타입 중 하나로 간주합니다. 그 이유는 구조적 타이핑에 따라, 이 객체가 A, B, C 중 하나의 구조를 충족시키면 타입 체크에서 유효하다고 판단하기 때문입니다.
- 이 객체는 a와 b 속성을 가지고 있으므로 A의 구조를 만족합니다.
- 이 객체는 a, b, c 속성을 가지고 있으므로 B의 구조도 만족합니다.
- 이 객체는 a, b, d 속성을 가지고 있으므로 C의 구조도 만족합니다.
따라서 타입스크립트는 이 객체가 A | B | C 타입 중 어느 하나에 해당한다고 판단하고, 에러를 발생시키지 않습니다.
4. Discriminated Unions (태그된 유니온)
유사한 구조의 타입을 확실히 구분하고 싶을 때는 Discriminated Unions(태그된 유니온)을 사용하는 것이 좋습니다. 이 방법에서는 공통 속성으로 구별자(discriminator)를 추가하여 타입을 명시적으로 구분할 수 있습니다. 예를 들어:
type A = {
kind: 'A';
a: string;
b: string;
}
type B = {
kind: 'B';
a: string;
b: string;
c: number;
}
type C = {
kind: 'C';
a: string;
b: string;
d: () => void;
}
type ABC = A | B | C;
const data: ABC[] = [
{
kind: 'A', // 명시적으로 A 타입임을 표시
a: 'a',
b: 'b',
c: 123,
d: () => {}
}, // 이제 에러가 발생할 것입니다.
];
이렇게 kind 속성을 사용하여 각 타입을 명확히 구분하면, 타입스크립트는 해당 객체가 실제로 A, B, C 중 어느 타입에 해당하는지를 명확히 판단할 수 있습니다.
5. 결론: 구조적 타이핑의 한계
구조적 타이핑은 매우 유연하고 강력하지만, 유사한 구조를 가진 타입들을 구분하는 데 어려움이 있을 수 있습니다. 타입스크립트는 객체의 구조만으로 타입을 판단하기 때문에, 유니온 타입에서 기대한 대로 동작하지 않을 수 있습니다.
이러한 한계를 극복하기 위해 Discriminated Unions 같은 방법이 사용됩니다. 하지만, 이는 구조적 타이핑과 덕 타이핑의 본질적인 특성에서 기인하는 것으로, 타입스크립트의 설계 철학에 맞게 유연성을 제공하면서도 안전성을 높이기 위한 해결책입니다.
'TYPESCRIPT' 카테고리의 다른 글
map() 메서드에서는 객체의 얕은 복사가 이루어지므로 리턴된 배열의 각 객체는 원본 배열의 참조를 그대로 가집니다. (0) | 2024.11.10 |
---|---|
우아한 타입스크립트 목차 (0) | 2024.09.10 |
타입스크립트의 고급 타이핑 기능들이 어떻게 보면 더 발전된 언어의 기능이라고 볼 수도 있지 않나요? 단순히 동적언어인 자바스크립트에 타이핑을 하기 위해 추가된 기능들인 건가요? (1) | 2024.08.26 |
타입스크립트의 타입 시스템은 타입에 대해 동작하는 순수 함수형 언어로 생각할 수 있습니다. (0) | 2024.08.26 |