타입은 값들의 집합을 대표하는 일종의 그릇
이 포스팅을 읽고 다시 한 번 생각해 보면 좋을 것 같습니다.
(더 좋은 방법은 이펙티브 타입스크립트 책 사서 읽기!)
타입스크립트가 동작하는 기본적인 규칙은 집합에 있습니다.
// script1
type StringOrNull = string | null
type Person = {
name: string;
};
type HavingBirth = {
birth: Date;
};
type LifeSpan = Person & HavingBirth; // { name: string; birth: Date; }
위와같은 타입형식은 타입스크립트를 이용하는 개발자라면 자주 보았던 모습일 겁니다.
그런데 가만히 살펴보면 조금 이상해 보입니다.
&
는 수학적으로 교집합을 의미하고, Person
과 HavingBirth
의 교집합은 없는거 아닌가요? HavingBirth
에 name
이 없고,Person
에 birth
가 없잖아요?
( 여기서 저와 비슷한 의문이 전혀 생기지 않는다면, 타입은 값들의 집합이라는 의미를 이해하신 분들이라 생각합니다. ㅎㅎㅎ)
// script2
interface Female {
name: string;
femaleProp:number;
}
interface Male {
name: string;
maleProp:string;
}
type Intersection = Female & Male // {name:string } ??? 틀린 생각
type Union = Female | Male // {name:string; femaleProp:number; maleProp:string;} ??? 틀린 생각
오히려 위와 같이 교집합이니까 name만 교집합이고, 나머지는 합집합으로 나타내야하는 것 아닐까요?
결론부터 말하자면 이러한 사고방식은 틀렸습니다. 적어도 타입스크립트에서는 말이죠.
// script3
interface Female {
name: string;
femaleProp:number;
}
interface Male {
name: string;
maleProp:string;
}
type FemaleAndMale = {
name: string;
femaleProp:number;
maleProp:string;
}
type Intersection = Female & Male // { name: string; femaleProp:number; maleProp:string;}
type Union = Female | Male // {name:string; femaleProp:number;}|{name:string; maleProp:string;}
사실 타입스크립트가 추론하는 방식은 위와 같습니다. 단어 자체의 의미와는 반대로, 교집합의 범위가 넓어지고, 합집합의 의미는 좁아진 것 같다는 느낌이 듭니다.
이는 타입스크립트의 타입이 속성에 대한 집합 이 아니라, 값들의 집합 으로 생각하기 때문입니다. (속성에 대한 집합이 script2와 같이 생각하는 것)
애초에 집합의 정확한 의미가 뭘까요? 위키백과에서는 집합은 주어진 성질을 만족시키는 대상들의 모임이다. 라고 정의합니다.
즉,
을 표현한 것이라고 풀어 말할 수 있습니다.
흠 아직 감이 잘 잡히지 않습니다. 위에서 얘기했던 타입들을 분석하면서 뽀개봅시다.
Intersction
타입을 분석해 볼까요?
Intersction
라는 타입은 Female
의 성질(name, femaleProp)과(and) Male
의 성질(name, maleProp)을 모두 만족하는 값들을 표현하는 방법(타입)이라고 해석할 수 있습니다. 그렇기 때문에 name, maleProp, femaleProp 모든 프로퍼티가 존재하는 값이 유효한 타입이 되는 겁니다.
Union
또한, Female
의 성질을 만족하거나(or) Male
의 성질을 만족하는 값들의 모임을 대표한다고 해석할 수 있습니다. 그렇기 때문에 Female이든 Male이든 두 성질중에 하나라도 만족하는 값이 있다면 Union타입에 만족하는 겁니다.
여기서 하나더 알 수 있는 사실은, 우리가 정의한 타입에 값을 할당 할 수 있냐 없냐를 판단한다는 겁니다. 결국 타입체크로 A에 B를 할당할 수 있는지 없는지 판단할 수 있는겁니다. 집합의 규칙을 이용해서...
즉, 타입은 값들의 집합을 대표하는 일종의 그릇인거죠.
벤다이어그램으로 그린다면 다음과 같습니다.
감이 조금 잡히실까요?
그렇기 때문에 우리는 FemaleOrMale 타입으로 선언된 변수에, Female조건을 만족하거나, Male조건을 만족하거나, 심지어 Female과Male의 조건을 동시에 만족하는 FemaleAndMale(type FemaleAndMale = {name:string; femaleProp:number; maleProp:string;}
)타입도 할당할 수 있는 겁니다.
= 해당 집합(타입)에 속할 수 있는(가능성 있는 모든) 값들의 집합
= 해당 타입에 할당할 수 있는 값들의 집합
= 'A는 B를 상속','A는 B에 할당 가능', 'A는 B의 서브타입' 이라는 말은 모두 A는 B의 부분집합이라는 의미(effective typescript 46p)
type NeverExample = string & number
type Foo = 'foo';
의 경우 'foo' 라는 리터럴 타입의 집합으로, 할당 가능한 값은 오직 'foo'밖에 없다.type StringOrNumber = string | number
타입은 string 혹은 number 아무 타입의 값이 할당될 수 있다는 뜻const foo:Foo='foo'
라는 변수 foo
도 StringOrNumber
인 타입의 변수에 할당할 수 있다.더 자세히 알고 싶으시다면 이펙티브 타입스크립트 46p를 보세요!
type A = {
one: string;
two: number;
};
type B = {
two: string;
};
type C = {
two: string[];
three: string;
};
type AandB = A & B; // {one:string; two:number & string; } two is never
type AandC = A & C; // {one:string; two:number & string[]; three:string;} two is never
type BandC = B & C; // { two:string & string[]; three:string;} two is never
type AandBandC = A & B & C; // {one:string; two:number & string & string[]; three:string;} two is never
type AorB = A | B;
type AorC = A | C;
type BorC = B | C;
type AorBorC = A | B | C;
// 아래 변수 1~11중에 타입에러를 내뿜는 변수는 무엇일까요?
const A_OR_B1: AorB = {} as A;
const A_OR_B2: AorB = {} as B;
const A_OR_B3: AorB = {} as C;
const A_OR_B4: AorB = {} as AandB;
const A_OR_B5: AorB = {} as AandC;
const A_OR_B6: AorB = {} as BandC;
const A_OR_B7: AorB = {} as AandBandC;
const A_OR_B8: AorB = {} as AorB;
const A_OR_B9: AorB = {} as AorC;
const A_OR_B10: AorB = {} as BorC;
const A_OR_B11: AorB = {} as AorBorC;
위의 질문을 타입체커의 힘을 빌리지 않고 찾아낸다면 타입은 값들의 집합이다라는 의미를 명확하게 이해한 것입니다.