타입 확장의 가장 큰 장점은 코드 중복을 줄일 수 있다는 것 (타입스크립트 코드를 작성하다보면 필연적으로 중복되는 타입 선언이 생기기 마련임) -> 중복되는 타입을 반복적으로 선언하는 것보다 기존에 작성한 타입을 바탕으로 타입 확장을 함으로써 불필요한 코드 중복을 줄일 수 있음
/**
* 메뉴 요소 타입
* 메뉴 이름, 이미지, 할인율, 재고 정보를 담고 있음
**/
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
Universal은 IdType과 Numeric의 교차 타입이므로 두 타입 모두 만족하는 경우에만 유지됨 -> 1, 2, 4번은 성립되지 않고 3번만 유효하므로 Universal의 타입은 number가 됨