Discriminated Union
에 대해서 알아보기 이전에 이전 포스트에 in operator narrwoing
을 보자. in operator narrowing의 단점이 존재한다.
type이 몇 개 없고 사람이 type에 따른 공통적인 property와 공통적이지 않은 property들을 인지하고 있을 때만 in operator narrowing
을 쉽게 이용할 수 있다.
하지만, 수 많은 interface에 수 많은 공통적인 property와 공통적이지 않은 property들이 존재한다면????🤔
각 type에 따른 property를 다 외울 것인가??? 난 못 외운다..... 천재라도 못 외우지 않을까...?
disadvantage of in operator narrowing
공통적인 property와 공통적이지 않은 property들을 알고 있어야
type narrowing
을 할 수 있다.
그러면 어떻게 이러한 문제점을 해결할 것인가에 대한 답이 discriminated union; 판별 유니온
이다.
판별 유니온(discriminated union)은 interface에 공통적인 property인 kind(convention상 kind로 기재하지만, 취향에 맞게 사용한다)
를 이용하여 type을 narrowing하는 것이다. 이 말만 보고서는 이해가 가지 않으니 아래 예시를 살펴보자.
각 동물들은 공통적인 속성과 공통적이지 않은 속성이 있다. 이에 대해 동물에 따라 울음소리를 출력하려고 한다.
interface Cow {
weight: number;
age: number;
canMakeMilk: boolean;
}
interface Dog {
weight: number;
age: number;
companionAnimal: boolean;
}
interface Pig {
weight: number;
age: number;
canBeSamGyeopSal: boolean;
}
type Animal = Cow | Dog | Pig;
function getSound(animal: Animal) {
// pseudo code
if Cow type -> MOO~~MOO~~
if Dog type -> Wal!!Wall!!
if Pig type -> Oink~~!!Oink~~!!
}
const cow: Cow = {
weight: 300,
age: 11,
canMakeMilk: true,
kind: "cow",
};
const dog: Dog = {
weight: 15,
age: 5,
companionAnimal: true,
kind: "dog",
};
const pig: Pig = {
weight: 200,
age: 7,
canBeSamGyeopSal: true,
kind: "pig",
};
getSound function의 pseudo code에서 in operator narrowing
을 사용한다면 아래와 같을 것이다.
if use in operator narrowing
function getSound(animal: Animal){
if ("canMakeMilk" in animal) return "MOO~~MOO~~";
if ("companionAnimal" in animal) return "Wal!!Wall!!";
if ("canBeSamGyeopSal" in animal) return "Oink~~!!Oink~~!!";
}
- 우유를 생산할 수 있는 property가 존재한다면 Cow Type일 것이므로 "MOO
MOO"를 return한다.- 반려동물인지 아닌지에 대한 property가 존재한다면 Dog Type일 것이므로 "Wal!!Wall!!"를 return한다.
- 삼겹살이 될 수 있는지 아닌지에 대한 property가 존재한다면 Cow Type일 것이므로 "Oink
!!Oink!!"를 return한다.
위와 같이 type에 따라 공통적이지 않은 property들을 알고 있어야 한다. 이제 내가 위에서 말한 in operator narrowing의 단점이 이해가 갈 것이다.
이제 위에 in operator narrwoing의 단점을 알았으니 대안인 discriminated narrowing을 이용해 보자.
기본적인 원리는 공통적인 property를 넣음으로써 이를 이용하여 type을 좁히는 것이다.
📚 kind(관례적으로 kind를 쓰지만 이는 원하는 대로 naming 해주면 된다)라는 property에
literal type
을 넣어 literal type과 일치하는지 아닌지로 type을 좁힐 수 있다.
그럼 공통적인 proeprty를 넣어보자
interface Cow {
...//
kind: 'cow';
}
interface Dog {
...//
kind: 'dog';
}
interface Pig {
...//
kind: 'pig';
}
이제 공통적으로 넣어준 kind property를 이용하여 literal type 검사를 진행하면 된다.
어떻게 진행하는지는 아래 코드를 살펴보자
// getSound function with discriminated union
function getSound(animal: Animal) {
switch (animal.kind) {
case "cow":
return "MOO~~MOO~~";
case "dog":
return "Wal!!Wall!!";
case "pig":
return "Oink~~!!Oink~~!!";
}
}
분기점을 설정해주는 것은 같지만 Cow type은 cow, Dog type은 dog라고 쉽게 알 수 있다. 이제 in operator narrowing에 비해 얼마나 편한지 시각적으로 느낄 수 있다!!
전체 코드
interface Cow {
weight: number;
age: number;
canMakeMilk: boolean;
kind: "cow";
}
interface Dog {
weight: number;
age: number;
companionAnimal: boolean;
kind: "dog";
}
interface Pig {
weight: number;
age: number;
canBeSamGyeopSal: boolean;
kind: "pig";
}
type Animal = Cow | Dog | Pig;
function getSound(animal: Animal) {
switch (animal.kind) {
case "cow":
return "MOO~~MOO~~";
case "dog":
return "Wal!!Wall!!";
case "pig":
return "Oink~~!!Oink~~!!";
}
}
const cow: Cow = {
weight: 300,
age: 11,
canMakeMilk: true,
kind: "cow",
};
const dog: Dog = {
weight: 15,
age: 5,
companionAnimal: true,
kind: "dog",
};
const pig: Pig = {
weight: 200,
age: 7,
canBeSamGyeopSal: true,
kind: "pig",
};
console.log("cow sounds like ->", getSound(cow));
console.log("dog sounds like ->", getSound(dog));
console.log("pig sounds like ->", getSound(pig));
결과
판별 유니온의 핵심은 kind라는 property에 type과 관련된 대표적인 내용을 literal type으로 지정함으로써 쉽게 type을 narrowing하는 것에 있는 것 같다. 이것을 배우면서 느낀 것은 literal type을 type을 대표하는 내용으로 기재를 해야될 것 같다는 것
이다.
이렇게 하지 않으면 결국에는 type을 정의한 곳에 가서 찾아봐야 하는... 머리 아픈... 상황이 발생할 것 같다.
따라서 type의 이름은 pascal case로 naming을 하니 literal type은 type 이름의 lower camel case로 작성하여 나만의 convention을 구축할 것이다.