[우아한타입리액트] 4장. 타입 확장하기.좁히기 (1)

Lina Hongbi Ko·2024년 11월 30일
0
post-thumbnail

4장. 타입 확장하기.좁히기

💡 타입 확장하기

  • 타입 확장 : 기존 타입을 사용해서 새로우 타입을 정의
  • 기본적으로 타입스크립트에서는 interface와 type 키워드를 사용해서 타입을 정의하고 extends, 교차 타입, 유니온 타입을 사용해 타입을 확장함

📍 타입 확장의 장점

  • 타입 확장의 가장 큰 장점은 코드 중복을 줄일 수 있다는 것 (타입스크립트 코드를 작성하다보면 필연적으로 중복되는 타입 선언이 생기기 마련임) -> 중복되는 타입을 반복적으로 선언하는 것보다 기존에 작성한 타입을 바탕으로 타입 확장을 함으로써 불필요한 코드 중복을 줄일 수 있음

    /**
    * 메뉴 요소 타입
    * 메뉴 이름, 이미지, 할인율, 재고 정보를 담고 있음
    **/
    interface BaseMenuItem {
        itemName: string | null;
        itemImageUrl: string | null;
        itemDiscountAmount: number;
        stock: number | null;
    }
    
    /**
    * 장바구니 요소 타입
    * 메뉴 타입에 수량 정보가 추가 되었음
    **/
    
    interface BaseCartItem extends BaseMenuItem {
        quantitiy: number;
    }
  • 메뉴 타입을 기준으로 타입을 확장해 장바구니 요소 타입을 정의함 -> 장바구니 요소는 메뉴 요소가 가지는 모든 타입이 필요함

  • 속성을 중복해 작성하지 않고 확장을 활용해 타입을 정의 -> 중복된 코드 줄이고 명시적인 코드를 작성하게 함

  • interface 키워드 대신 type을 쓴다면 아래처럼 작성

    type BaseMenuItem = {
        itemName: string | null;
        itemImageUrl: string | null;
        itemDiscountAmount: number;
        stock: number | null;
    };
    
    type BaseCartItem = {
        quantity: number;
    } & BaseMenuItem;
  • 앞에서 정의한 BaseCartItem을 활용하면 요구사항이 늘어날 때마다 새로운 CartItem 타입을 확장하여 정의할 수 있음

    /**
    * 수정할 수 있는 장바구니 요소 타입
    * 품절 여부, 수정할 수 있는 옵션 배열 정보가 추가되었다
    */
    
    interface EditableCartItem extends BaseCartItem {
        isSoldOut: boolean;
        optionGroups: SelectableOptionGroup[];
    }
    
    /**
    * 이벤트 장바구니 요소 타입
    * 주문 가능 여부에 대한 정보가 추가되었다
    */
    
    interface EventCartItem extends BaseCartItem {
    	orderable: boolean;
    }
  • BaseCartItem을 확장해 EditableCartItem, EventCartItem 타입을 만들어서 장바구니와 관련된 요구 사항이 생길 때마다 필요한 타입을 손쉽게 만들 수 있음

  • 기존 장바구니 요소에 대한 요구 사항이 변경되어도 BaseCartItem을 타입만 수정하면 됨

📍 유니온 타입

  • 타입을 2개 이상 조합해 사용하는 방법

  • 집합 관점으로 보면 유니온 타입을 합집합으로 해석할 수 있음

    type MyUnion = A | B;
  • A와 B의 유니온 타입인 MyUnion은 타입 A와 B의 합집합 (= A타입과 B타입의 모든 값이 MyUnion 타입의 값이 됨)

  • 주의해야 할 점 : 유니온 타입으로 선언된 값은 유니온 타입에 포함된 모든 타입이 공통으로 갖고 있는 속성에만 접근 할 수 있음

    interface CookingStep {
        orderId: string;
        price: number;
    }
    
    interface DeliveryStep {
        orderId: string;
        time: number;
        distance: string;
    }
    
    function getDeliveryDistance(step: CookingStep | DeliveryStep) {
        return step.distance;
        // Property 'distance' does not exist on type 'CookingStep | DeliveryStep'
        // Property 'distance' does not exist on type 'CookingStep'
    }
  • getDeliveryDistance 함수는 CookingStep과 DeliveryStep의 유니온 타입 값을 step 이라는 인자로 받고 있음.
    함수 본문에서 step.distance를 호출하고 있는데 distance는 DeliveryStep에만 존재하는 속성이다. 인자로 받는 step의 타입이 CookingStep 이라면 distance 속성을 찾을 수 없기 때문에 에러가 발생함

    *타입스크립트의 타입을 속성의 집합이 아니라 값의 집합이라고 생각해야 유니온 타입이 합집합이라는 개념을 이해할 수 있다.
    참조: https://velog.io/@dltlsgh5/typescript%ED%83%80%EC%9E%85%EC%9D%84-%EC%A7%91%ED%95%A9%EC%9C%BC%EB%A1%9C-%EC%83%9D%EA%B0%81%ED%95%98%EA%B8%B0

  • step 이라는 유니온 타입은 CookingStep과 또는 DeliveryStep의 타입에 해당할 뿐이지 CookingStep 이면서 DeliveryStep인 것은 아님

📍 교차 타입

  • 기존 타입을 합쳐 필요한 모든 기능을 가진 하나의 타입으로 만드는 것

  • 유니온 타입과 다른 점이 있다면, 타입을 합쳐 모든 속성을 가진 단일 타입이 됨

    interface CookingStep {
        orderId: string;
        time: number;
        price: number;
    }
    interface DeliveryStep {
        orderId: string;
        time: number;
        distance: string;
    }
    
    type BaedalProgress = CookingStep & DeliveryStep;
  • BaedalProgress는 CookingStep과 DeliveryStep 타입을 합친 타입임

    function logBaedalInfo(progress: BaedalProgress) {
        console.log(`주문 금액: ${progress.price}`);
        console.log(`배달 거리: ${progress.distance}`);
    }
  • BaedalProgress 타입의 progress 값은 CookingStep이 갖고 있는 price 속성과 DeliveryStep이 갖고 있는 distance 속성을 포함하고 있음!

    type MyIntersection = A & B;
  • 유니온타입이 합집합의 개념이라면 교차 타입은 교집합의 개념과 비슷함

  • MyIntersection 타입의 모든 값은 A 타입의 값이며, MyIntersection 타입의 모든 값은 B의 값임 (집합의 관점에서 해석해보면, 집합 MyIntersection의 모든 원소는 집합 A의 원소이자 집합 B의 원소임)

    *다시 말하지만 타입스크립트의 타입을 속성의 집합이 아니라 값의 집합으로 이해해야함!! BaedalProgress 교차 타입은 CookingStep이 가진 속성과 DeliveryStep가진 속성을 모두 만족하는 값의 타입이라고 해석할 수 있음

      /* 배달 팁 */
      interface DeliveryTip {
          tip: string;
      }
    
      /* 별점 */
      interface StarRating {
          rate: number;
      }
    
      /* 주문 필터 */
      type Filter = DeliveryTip & StarRating;
    
      const filter: Filter {
          tip: "1000원 이하",
          rate: 4,
      };
  • 타입의 속성이 아닌 값의 집합으로 해석했기 때문에 교차타입 Filter은 DeliveryTip의 tip 속성과 StarRating의 rate 속성을 모두 만족하는 값이 됨

  • 교차 타입을 사용할 때 타입이 서로 호환되지 않는 경우도 있음

    type IdType = string | number;
    type Numeric = number | boolean;
    
    type Universal = IdType & Numeric;
  • Universal 타입을 4가지로 생각해 볼 수 있는데,
    1. string 이면서 number

    1. string 이면서 boolean
    2. number 이면서 number
    3. number 이면서 boolean
  • Universal은 IdType과 Numeric의 교차 타입이므로 두 타입 모두 만족하는 경우에만 유지됨 -> 1, 2, 4번은 성립되지 않고 3번만 유효하므로 Universal의 타입은 number가 됨

profile
프론트엔드개발자가 되고 싶어서 열심히 땅굴 파는 자

0개의 댓글