Search
🔠

조건부 타입

1. extends와 제네릭을 활용한 조건부 타입

T extends U ? X : Y // 타입 T를 U에 할당할 수 있으면 X 타입 // 타입 T를 U에 할당할 수 없으면 Y 타입
TypeScript
복사
interface Bank { financialCode: string; fullName: string; // Card와의 차이 name: string; fullName: string; } interface Card { financialCode: string; appCardType?: string; // Bank와의 차이 name: string; fullName: string; } // 제네릭 타입으로 extends를 사용한 조건부 타입 type PayMethod<T> = T extends "card" ? Card : Bank; // 제네릭 매개변수 = "card" : Card 타입 type CardPayMethodType = PayMethod<"card">; // 제네릭 매개변수 != "card" : Bank 타입 type BankPayMethodType = PayMethod<"bank">;
TypeScript
복사
extends 는 보통 문자열 리터럴과 함께 사용하진 않지만 예시를 위해 사용

2. 조건부 타입을 사용하지 않았을 때의 문제점

type PayMethodType = PayMethodInfo<Card> | PayMethodInfo<Bank>; export const useGetRegisteredList = ( type: "card" | "appcard" | "bank" ): UseQueryResult<PayMethodType[]> => { const url = `baeminpay/codes/${type === "appcard" ? "card" : type}`; const fetcher = fetcherFactory<PayMethodType[]>({ onSuccess: (res) => { const usablePocketList = res?.filter( (pocket: PocketInfo<Card> | PocketInfo<Bank>) => pocket?.useType === "USE" ) ?? []; return usablePocketList; }, }); // 반환값의 타입을 명확하기 추론하지 못함 const result = useCommonQuery<PayMethodType[]>(url, undefined, fetcher); return result; };
TypeScript
복사
위 코드를 만든 개발자는 useGetRegisteredList가 인자의 타입으로 "card", "appcard", "bank" 중 하나를 받아 해당 타입과 알맞은 타입으로 반환까지 해내기를 원했다.
"card", "appcard" => Card
"bank" => Bank
때문에 타입 PayMethodType을 Card 또는 Bank 타입의 PatMethodInfo 중 하나로 고정하고 반환값 result에 PayMethodType[] 타입을 명시해주었다. 하지만 Card와 Bank 를 명확히 구분하는 로직이 없다. 이것이 문제가 된다.
사용자가 인자로 "card"를 전달했을 때 함수가 반환하는 타입이 PayMethodInfo<Card>[]였으면 좋겠지만, 타입 설정이 유니온(|)으로만 되어있기 때문에 구체적으로 추론할 수 없다.
즉, useGetRegisteredList는 인자로 넣는 타입에 알맞은 타입을 반환하지 못하는 함수다. 유니온 외 다른 조치가 필요하다.

(3) extends 조건부 타입을 활용하여 개선하기

extends 조건부 타입을 활용하여 하나의 API 함수에서 타입에 따라 정확한 반환 타입을 추론하게 만들 수 있다. 또한 extends를 제네릭의 확장자로 활용해서 "card", "appcard", "bank" 외 다른 값이 인자로 들어오는 경우도 방어한다.
// before type PayMethodType = PayMethodInfo<Card> | PayMethodInfo<Bank>; // after type PayMethodType<T extends "card" | "appcard" | "bank"> = T extends | "card" | "appcard" ? Card : Bank; // PayMethodType의 제네릭으로 받은 값이 "card" 또는 "appcard"면 PayMethodInfo<Card> 타입을 반환 // PayMethodType의 제네릭으로 받은 값이 이외의 값이면 PayMethodInfo<Bank> 타입을 반환 // +) "card" 앞 | 는 줄바꿈 구분자로 이해하면 편함. 한줄로 명확하게 표현하면 아래와 같음 // T extends ("card" | "appcard") ? Card : Bank;
TypeScript
복사
새롭게 정의한 PayMethodType에 제네릭 값을 넣어주기 위해 useGetRegisteredList 함수 인자의 타입을 넣어준다.
// before export const useGetRegisteredList = ( type: "card" | "appcard" | "bank" ): UseQueryResult<PayMethodType[]> => { /* ... */ const result = useCommonQuery<PayMethodType[]>(url, undefined, fetcher); return result; }; // after export const useGetRegisteredList = <T extends "card" | "appcard" | "bank">( type: T ): UseQueryResult<PayMethodType<T>[]> => { /* ... */ const result = useCommonQuery<PayMethodType<T>[]>(url, undefined, fetcher); return result; };
TypeScript
복사
이렇게 조건부 타입을 활용함으로써
인자로 "card" 또는 "appcard"를 받으면 PayMethodInfo<Card>를 반환하고
인자로 "bank"를 받으면 PayMethodInfo<Bank>를 반환한다.
이에 따라 불필요한 타입 가드와 불필요한 타입 단언을 하지 않아도 된다.