Typescript - Intersection하면 합쳐지는 이

김재환·2023년 12월 28일
0

TypeScript

목록 보기
4/7

앞에서 Union과 Intersection을 배웠습니다. 간단하게는 A | B라고 하면 "A 타입이거나 B 타입이다", A & B라고 하면 "A 타입이랑 B 타입을 합친 것이다"라고 이해할 수 있었는데요. 그런데 자세히 생각해 보면 헷갈리는 부분이 있습니다. 혹시 수학에서 집합에 대해 배웠던 분들이라면 Union(합집합), Intersection(교집합)과 같은 용어를 들었을 때 아래와 같은 그림을 떠올리셨을 텐데요. 두 타입을 Union 하면 왠지 두 타입의 형태를 합쳐야 할 거 같고, Intersection을 하면 두 타입의 공통된 부분만 타입으로 될 거 같습니다. 그런데 Intersection을 하면 두 타입의 모양을 합친다니까 뭔가 이상합니다. 왜 그럴까요? 이번 레슨에서는 이 부분에 대해서 자세히 알아보도록 하겠습니다.

Structural Subtyping

타입스크립트에서 타입은 Structural Subtyping이라는 규칙을 따릅니다. 쉽게 말해서 구조가 같으면 같은 타입이라고 판단하는 건데요. 예를 들어서 a라는 프로퍼티를 갖는 타입 A가 있다고 해보죠. 이 타입의 a 프로퍼티를 출력하는 printA()라는 함수가 있습니다. 이 함수를 아래 코드와 같이 { a: 'codeit' }, { a: 'codeit', b: 42 }, { a: 'codeit', b: 42, c: true }라는 객체로 실행해도 모두 올바른 타입입니다. 세 객체 모두에 a라는 프로퍼티가 있기 때문에 타입 A라고 판단하는 거죠. 이런 걸 Structural Subtyping, Structural Type System이라고 부릅니다.


interface A {
  a: string;
}

interface B {
  b: number;
}

function printA(arg: A) {
  console.log(arg.a);
}

const x = { a: 'codeit' };
const y = { b: 42 };
const z = { a: 'codeit', b: 42 };
const w = { a: 'codeit', b: 42, c: true };

printA(x);
printA(y); // 잘못된 타입
printA(z);
printA(w);

용어가 조금 어려워 보입니다. 쉽게 생각해서 "같은 모양이 있으면 같은 타입이라고 판단한다"라고 이해할 수 있습니다. 앞으로의 내용을 설명하기 위해서 간단하게 아래와 같은 그림으로 나타내 볼게요. 원 안에 있는 것들은 타입 A에 해당하는 객체들입니다.

마찬가지로 타입 B까지 그림으로 표현하면 아래와 같이 표현할 수 있을 겁니다.

Union 타입 살펴보기

앞에서 Union 타입은 "A 타입이거나 B 타입이다"라고 표현했습니다. 이걸 보다 정확하게 그림으로 이해해 보면 아래와 같습니다. 그래서 { a: 'codeit' }과 { b: 42 }는 물론이고 { a: 'codeit', b: 42 }도 타입 A | B라고 사용할 수 있습니다.

코드로 확인해 봐도 printAUnionB()라는 함수에 모두 타입 오류 없이 사용할 수 있다는 걸 알 수 있죠. 참고로 함수 안에서 if문으로 in 키워드를 사용해서 해당하는 프로퍼티가 존재하는지 확인해 봤는데요. 이런 식으로 타입의 범위를 좁힐 수도 있습니다. 타입스크립트에서는 이런 걸 Type Narrowing이라고 표현하니까 참고로 알아두시면 좋을 거 같네요.


interface A {
  a: string;
}

interface B {
  b: number;
}

function printAUnionB(arg: A | B) {
  // 여기서는 타입 A | B

    if ('a' in arg) {
    // 여기 안에서는 타입 A
    console.log(arg.a);
  }

    if ('b' in arg) {
    // 여기 안에서는 타입 B
    console.log(arg.b); // VS Code에서 arg에 마우스를 호버해 보세요.
  }
}

const x = { a: 'codeit' };
const y = { b: 42 };
const z = { a: 'codeit', b: 42 };
const w = { a: 'codeit', b: 42, c: true };

printAUnionB(x);
printAUnionB(y);
printAUnionB(z);
printAUnionB(w);

Intersection 타입 살펴보기

마찬가지로 Interesection을 살펴봅시다. 앞에서 A & B라고 하면 "A와 B 두 타입을 합친다"라고 간단히 표현했습니다. 이걸 보다 정확하게 그림으로 이해해 보면 아래와 같습니다. { a, b } 두 프로퍼티를 모두 가지고 있는 타입들이 A & B라고 할 수 있겠죠? 그래서 객체의 모양만 본다면 마치 Intersection이 두 타입의 모양을 합치는 것처럼 생각할 수 있습니다.


interface A {
  a: string;
}

interface B {
  b: number;
}

function printAIntersectionB(arg: A & B) {
  console.log(arg.a);
  console.log(arg.B);
}

const x = { a: 'codeit' };
const y = { b: 42 };
const z = { a: 'codeit', b: 42 };
const w = { a: 'codeit', b: 42, c: true };

printAIntersectionB(x); // 타입 오류
printAIntersectionB(y); // 타입 오류
printAIntersectionB(z);
printAIntersectionB(w);

정리

이번 레슨에서는 Structural Subtyping이라는 개념을 통해서, Union과 Intersection에 대해 그림으로 더 자세하게 알아보았습니다. 간단하게는 A | B라고 하면 "A 타입이거나 B 타입이다", A & B라고 하면 "A 타입이랑 B 타입을 합친 것이다"라고 생각하면 대부분의 경우 문제없이 사용하실 수 있을 텐데요. 혹시 앞으로 타입스크립트를 사용하면서 interface의 상속을 활용하거나, 여러 타입을 조합해서 복잡한 타입을 다루게 될 때 오늘 배운 이 내용을 토대로 한번 생각해 보시면 혼란을 줄이는데 도움이 되실 겁니다.

profile
안녕하세요

0개의 댓글