타입확장 : 기존타입을 사용 -> 새로운 타입을 정의하는 것
타입정의( type, interface ) -> 타입확장( extends, 교차 타입, 유니온 타입)
.
.
.
1) 코드 중복을 줄일 수 있다.
(필연적으로 중복되는 타입 생김 -> 타입확장으로 불필요한 코드 중복 줄임.)
/**
* 메뉴 요소 타입
* 메뉴 이름, 이미지, 할인율, 재고 정보를 담고 있다
**/
interface BaseMenuItem {
itemName: string | null;
itemImageUrl: string | null;
itemDiscountAmount: number;
stock: number | null;
}
/**
* 장바구니 요소 타입
* 메뉴 타입에 수량 정보가 추가되었다
**/
interface BaseCartItem extends BaseMenuItem {
quantity: number;
}
장바구니 요소 : 메뉴요소가 가지는 모든 타입이 필요
BaseCartItem : BaseMenuItem에서 확장되었다는 걸 알 수 있음.
type BaseMenuItem = {
itemName: string | null;
itemImageUrl: string | null;
itemDiscountAmount: number;
stock: number | null;
};
type BaseCartItem = {
quantity: number;
} & BaseMenuItem;
2) 확장성
/**
* 수정할 수 있는 장바구니 요소 타입
* 품절 여부, 수정할 수 있는 옵션 배열 정보가 추가되었다 *
**/
interface EditableCartItem extends BaseCartItem {
isSoldOut: boolean;
optionGroups: SelectableOptionGroup [];
}
/**
* 이벤트 장바구니 요소 타입
* 주문 가능 여부에 대한 정보가 추가되었다 *
**/
interface EventCartItem extends BaseCartItem {
orderable: boolean;
요구사항이 늘어날 때마다 새로운 CartItem타입을 확장하여 정의할 수 있다.
타입을 활용하면 장바구니와 관련된 요구 사항이 생길 때마다 필요한 타입을 손쉽게 만들 수 있다.
BaseCartItem 타입만 수정하고 EditableCartItem이나 EventCartItem은 수정하지 않아도 되기 때문에 효율적이다.
: 2개 이상의 타입을 조합하여 사용하는 방법
type MyUnion = A | B
: MyUnion은 타입 A와 B의 합집합이다.
모든 원소는 집합 MyUnion의 원소이며, 집합 B의 모든 원소 역시 집합 MyUnion의 원소이다.
-> 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이라는 인자로 받고 있다.
: 기존 타입을 합쳐 필요한 모든 기능을 가진 하나의 타입을 만드는 것.
interface CookingStep {
orderId: string;
time: number;
price: number;
}
interface DeliveryStep {
orderId: string;
time: number;
distance: string;
}
type BaedalProgress = CookingStep & DeliveryStep;
유니온 타입과 다른 점 ) BedalProgresssms CookingStep과 DeliveryStep타입을 합쳐 모든 속성을 가진 단일 타입이 된다.
function logBaedalInfo(progress: BaedalProgress) {
console.log(`주문 금액: ${progress.price}`);
console.Log(`배달 거리: ${progress.distance}`);
-> BedalProgress타입의 progress 값 =
CookingStep이 갖고 있는 price 속성 + DeliveryStep이 갖고 있는 distance 속성
type MyIntersection = A & B;
유니온 타입 = 합집합의 개념
교차 타입 = 교집합의 개념
MyIntersection 타입의 모든 값 = A타입의 값
MyIntersection 타입의 모든 값 = B타입의 값
-> MyIntersection 타입의 모든 원소 = 집합 A의 원소 = 집합 B의 원소
/* 배달 팁 */
interface DeliveryTip {
tip: string;
}
/* 별점 */
interface StarRating {
rate: number;
}
/* 주문 필터 */
type Filter = DeliveryTip & StarRating;
const filter: Filter = {
tip: "1000원 이하",
rate: 4,
};
Filter의 타입1 : DeliveryTip 속성 + StarRating 속성 (모두 포함) <- never타입 x
-> 교차 타입 Filter는 DeliveryTip의 tip 속성 + StarRating의 rate 속성
type IdType = string | number;
type Numeric = number | boolean;
type Universal = IdType & Numeric;
Universal = IdType과 Numeric의 교차 타입
-> 두 타입을 모두 만족하는 경우에만 유지됨
=> Universal의 타입은 number
: extends 키워드를 사용해 교차 타입을 작성 가능
interface BaseMenuItem {
itemName: string | null;
itemImageUrl: string | null;
itemDiscountAmount: number;
stock: number | null;
}
interface BaseCartItem extends BaseMenuItem = {
quantity: number;
}
: BaseCartItem은 BaseMenuItem을 확장 -> BaseMenuItem의 속성을 모두 포함.
interface BaseMenuItem {
itemName: string | null;
itemImageUrl: string | null;
itemDiscountAmount: number;
stock: number | null;
}
type BaseCartItem = {
quantity: number;
} & BaseMenuItem;
const baseCartItem: BaseCartItem = {
itemName : "지은이네 떡볶이",itemImageUrl: "https://www.woowahan.com/images/jieun-tteokbokkio.png",
itemDiscountAmount: 2000,
stock: 100,
quantity: 2,
}
: BaseCartItem ( 단일 타입 ) = quantity라는 새로운 속성 + BaseMenuItem의 모든 속성
주의) extends 키워드를 사용한 타입이 교차 타입과 100% 상응하지는 않는다는 것. ( 아래 예시 참조 )
interface DeliveryTip {
tip: number;
}
interface Filter extends DeliveryTip {
tip: string;
}
// Interface "Filter' incorrectly extends interface 'DeliveryTip'
// Types of property 'tip' are incompatible
// Type 'string' is not assignable to type 'number'
DeliveryTip 타입은 number 타입의 tip 속성을 가짐.
DeliveryTip을 extends로 확장한 Filter 타입에 string타입의 속성 tip을 선언 -> tip의 타입에 호환되지 않는 에러 발생.
그러나!!
type DeliveryTip = {
tip: number;
}
type Filter = DeliveryTip & {
tip: string;
}
extends -> & : 이렇게 바꾸면 에러 발생 x
type의 키워드는 교차 타입으로 선언 되었을 때 : 새롭게 추가되는 속성에 대해 미리 알 수 없기 때문에 에러가 발생 x
하지만 tip이라는 같은 속성에 대해 서로 호환되지 않는 타입이 선언되어 결국 never타입이 된 것.
/**
* 메뉴에 대한 타입
* 메뉴 이름과 메뉴 이미지에 대한 정보를 담고 있다
**/
interface Menu {
name: string;
image: string;
}
: Menu 인터페이스를 기반으로 사용자에게 메뉴그림들을 보여줌.
function MainMenu() {
// Menu 타입을 원소로 갖는 배열
const menuLIst: Menu[]= [{name: 1인분, image: "1인분•png"}, ....]
return (
<ul>
{menuList.map ((menu) => (
‹li›
<img src={menu.image} />
<span>{menu.name}</span>
</li>
))}
</ul>
)
}
: 요구 사항을 만족하는 타입의 작성방법 2가지
1. 특정 메뉴를 길게 누르면 gif 파일이 재생되어야 함.
2. 특정 메뉴는 이밎 대신 별도의 텍스트만 노출되어야 함.
/**
* 방법1. 타입 내에서 속성 추가
* 기존 Menu 인터페이스에 추가된 정보를 전부 추가
**/
interface Menu {
name: string;
image: string;
gif?: string; // 요구 사항 1. 특정 메뉴를 길게 누르면
text?: string; // 요구 사항 2. 특정 메뉴는 이미지 대신 별도의 텍스트만 노출되어야 한다.
}
/**
* 방법2. 타입 확장 활용
* 기존 Menu 인터페이스는 유지한 채, 각 요구 사항에 따른 별도 타입을 만들어 확장시키는 구조
**/
interface Menu {
name: string;
image: string;
}
/**
*gif를 활용한 메뉴 타입
* Menu 인터페이스를 확장해서 반드시 gif 값을 갖도록 만든 타입
**/
interface SpecialMenu extends Menu {
gif: string; // 요구 사항 1. 특정 메뉴를 길게 누르면 gif 파일이 재생되어야 한다
}
/**
* 별도의 텍스트를 활용한 메뉴 타입
* Menu 인터페이스를 확장해서 반드시 text 값을 갖도록 만든 타입 **/
interface PackageMenu extends Menu {
text: string; // 요구 사항 2. 특정 메뉴는 이미지 대신 별도의 텍스트만 노출되어야 한다
}
/**
* 각 배열은 서버에서 받아온 응답 값이라고 가정 ( 3가지 )
**/
const menuList = [
{ name: "찜", image: "찜.png" },
{ name: "찌개", image: "찌가.png" },
{ name: "회", image: "회.png" },
];
const specialMenuList = [
{name: 돈까스, image: "돈까스•png", gif: "돈까스.gif" },
{ name : "피자", image: "피자.png", gif: "피자.gif" },
];
const packageMenuList = [
{ name : "1인분", image: "1인분•png", text: "1인 가구 맞춤형" },
{ name: "족발", image: "족발.png", text: "오늘은 족발로 결정" },
];
각 메뉴 목록은 Menu[ ]로 표현
menuList: Menu[] // 0K
specialMenuList: Menu[] // 0K
packageMenuList: Menu[] // 0K
pecialMenuList.map (menu) => menu.text); // TypeError: Cannot read properties of undefined
: 에러 발생
왜? specialMenuList 배열의 모든 우너소는 text라는 속성을 가지고 있지 않아서
: 각 배열의 타입을 확장할 타입에 맞게 명확히 규정 가능.
enuList: Menu[] // 0K
specialMenuList: Menu[] // NOT OK
specialMenuList: SpecialMenu[] // 0K
packageMenuList: Menu[] // NOT OK
packageMenuList: PackageMenu[] // 0K
specialMenuList 배열의 원소 내 속성에 동일하게 접근한다고 가정,
프로그램을 실행하지 않고도 타입이 잘못되었음을 인지 가능.
specialMenuList.map (menu) => menu.text); // Property 'text' does not exist on type 'specialMenu'
주어진 타입에 무분별하게 속성을 추가하여 사용 X -> 타입을 확장해서 사용하는 걸 지향 ( 버그도 예방 가능 )
도서참조 : 우아한 타입스크립트 with 리액트