[TS] Union과 Intersection & Union vs Enum

Union

Unionor연산자라고 보면 된다.

  • 원시 타입
function logPrimitiveType(value: string | number | boolean): void {
  // string 이거나 number 이거나 boolean
  console.log(value);
}
  • 객체 타입
interface Person {
  name: string;
  age: number;
}

interface Developer {
  name: string;
  kindOf: string;
}

function logObjectType(value: Person | Developer): void {
  // Person 이거나 Developer
  console.log(value)
}

객체타입의 경우 프로퍼티를 사용하려 할 때 PersonDeveloper의 공통 프로퍼티만 error없이 사용할 수 있고 공통되지않는 프로퍼티는 타입가드를 거쳐야만 error없이 사용할 수 있다.

타입가드란?: 특정 타입으로 타입의 범위를 좁혀나가는(필터링 하는) 과정


function logObjectType(value: Person | Developer): void {
  // OK
  console.log(value.name)

  // Error
  console.log(value.age)
  console.log(value.kindOf)
}

이를 이해하며 or연산자는 이것도 되고 저것도 되는 것이기에 타입들 내부의 모든 프로퍼티를 사용할 수 있어야되지 않나? 싶었지만 함수 내부에서 사용될 값의 특징을 생각해보면 위와 같은 방식이 옳다는 것을 알 수 있다.

우리는 Person | Developer를 사용하기를 원한다.
그 말인 즉슨 값으로 Person이 들어올지 Developer가 들어올지 알 수 없다라는 것이다.

때문에 타입을 추론할 때 둘 중에 어느 것을 사용해도 상관없는 프로퍼티인 name은 타입가드없이 사용할 수 있고 agekindOf는 타입가드 과정을 거쳐야만 안전한 코드를 작성할 수 있으므로 위와 같이 동작한다.



Intersection

Intersectionand연산자라고 보면 된다.

  • 원시 타입
function logPrimitiveType(value: string & number & boolean): void {
  // value === never
  console.log(value);
}
  • 객체 타입
interface Person {
  name: string;
  age: number;
}

interface Developer {
  name: string;
  kindOf: string;
}

function logObjectType(value: Person & Developer): void {
  // Person 이면서 Developer
  console.log(value)
}

intersection의 경우는 실무에서 잘 쓰이지 않는다고 한다.
그 이유를 알아보자.

원시타입

우선 원시타입의 경우 하위타입과 intersection하지 않는 이상 never로 추론된다.
위의 예시로 보면 string이면서 number이면서 boolean이어야하는데 이는 사실상 불가능한 타입이기 때문이다.

만약 booleannullintersection하면 null로 판정된다.

때문에 원시타입에서는 사실상 intersection을 사용할 필요가 없다.

객체타입

객체 타입의 경우 역시 굳이 사용할 필요가 없다.


function logObjectType(value: Person & Developer): void {
  // OK
  console.log(value.name)
  console.log(value.age)
  console.log(value.kindOf)
}

위의 예시를 보면 Person이면서 Developervalue를 받기 때문에 함수 몸체에서 내부 프로퍼티를 모두 사용할 수 있어 편해보이지만 intersectionand연산자와 같다고 했다.

이게 무슨 뜻일까?

한마디로 PersonDeveloper 둘 모두를 충족시키는 값이 전달되어야 한다는 것이다.

당연히 PersonDeveloper모두를 충족하는 값이 전달될 것이기 때문에 함수 몸체에서 둘 모두의 프로퍼티를 사용해도 문제가 없던 것이다.
이게 과연 장점으로 작용할 수 있을까?
아마 호출 시점을 보면 얘기가 달라질 것이다.

logObjectType({ name: 'son', age: 27, kindOf: 'front' });

위의 호출문을 보면 Person이면서 Developer인 객체를 넣어주었다.
하나라도 만족하지 않는다면 타입에러가 난다.
그럼 생각해보자 { name: 'son', age: 27, kindOf: 'front' } 이 녀석은 무슨 타입일까?

그렇다, 사실 상 객체타입에서 intersection은 새로운 타입을 생성하는 것과 다른게 없다.
하지만 해당 타입을 명시하고 사용하는 것이 아니기 때문에 예측도가 떨어진다.

때문에 intersection을 쓸거면 차라리 새로운 타입을 확장하거나 생성해서 사용하는 것이 훨씬 안전하다.



Enum vs Union

이번에 unionenum을 공부해보며 각각의 장단점을 비교해보고 무엇을 사용하는 것이 더 유리할지 고민해보았다.

Enum

장점

먼저 enum의 경우 내부 속성을 자동완성해주는 기능을 제공하기 때문에 속성 값을 몰라도 추상객체명만 알면 된다. 라는 장점이 있다.

하지만 union 역시 특정 타입이 아닌 값을 명시해주면 enum처럼 자동완성 기능을 사용할 수
있다.

단점

하지만 enumjs 내부적으로 지원하지 않기 때문에 js로 컴파일하면 객체가 생성된다.
이는 곧 런타임에 영향을 미친다는 것인데 타입을 사용할 뿐인데 런타임에 영향을 미친다는 것이 아쉽다.

물론 const 키워드를 붙이면 컴파일을 하더라도 객체가 생성되지 않지만 이를 떠나서도 union과 대비되게 import구문이 필요하다는 점 역시 단점으로 작용한다.

Union

장점

  1. 타입가드만 잘해주면 type에 따른 api를 구분하여 사용할 수 있다.
  2. import하지 않아도 타입 힌트를 제공한다.
  3. 런타임에 영향을 주지 않는다.

결론

enum이 불편하거나 좋지 않은 자료형은 아니지만 union을 썼을 때의 장점이 좀 더 명확하기 때문에 여러 값을 가진 타입을 만들고 싶을 때 union을 더 선호하려 한다.


참고자료

타입스크립트 꿀팁

좋은 웹페이지 즐겨찾기