Type Narrowing-3(Discriminated Union; 판별 유니온)

GI JUNG·2022년 12월 22일
4

typescript

목록 보기
4/10
post-thumbnail

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

판별 유니온(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",
};

🤔 in operator narrowing의 단점

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~~!!";
}
  1. 우유를 생산할 수 있는 property가 존재한다면 Cow Type일 것이므로 "MOOMOO"를 return한다.
  2. 반려동물인지 아닌지에 대한 property가 존재한다면 Dog Type일 것이므로 "Wal!!Wall!!"를 return한다.
  3. 삼겹살이 될 수 있는지 아닌지에 대한 property가 존재한다면 Cow Type일 것이므로 "Oink!!Oink!!"를 return한다.

위와 같이 type에 따라 공통적이지 않은 property들을 알고 있어야 한다. 이제 내가 위에서 말한 in operator narrowing의 단점이 이해가 갈 것이다.

🎉 in operator 단점을 discriminated 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을 구축할 것이다.

📚 참고

type narrowing with discriminated union by typescript docs

profile
step by step

0개의 댓글