ko id ko ko ko ko es en en ko ko ko en ko ko en ko en ko ko ko ko ko en ko ko ko ko ko ko en ko ko ko ko ko en ko ko en ko en ko ko en en ko en en en
TypeScript 배우기 - 11. 조건부 타입
조건부 타입은 입력 타입과 출력 타입간의 관계를 설명하는데 도움이 됩니다. 다음은 핸드북에 나온 예제 입니다.
조건부 타입은 다음과 같이 표현됩니다.
SomeType extends OtherType ? TrueType : FalseType;
SomeType이 OtherType으로 할당할 수 있으면 TrueType이 되고 그렇지 않으면 FalseType이 됩니다.
위의 코드는 너무나 간단해서 조건부 타입이 정말로 유용하게 쓰일 수 있는지 알기가 어려운데요, 제네릭과 함께 사용될 때 그 힘이 발휘됩니다. 다음의 코드를 보시죠.
interface IdLabel { id: number /* some fields */; } interface NameLabel { name: string /* other fields */; } function createLabel(id: number): IdLabel; function createLabel(name: string): NameLabel; function createLabel(nameOrId: string | number): IdLabel | NameLabel; function createLabel(nameOrId: string | number): IdLabel | NameLabel { throw "unimplemented"; }
앞서 학습했던 오버로드 함수군요. 장황합니다. 이것을 제네릭 함수와 함께 조건부 타입으로 다음과 같이 표현할 수 있습니다.
type NameOrId  = T extends number ? IdLabel : NameLabel;
오, 제네릭 인자의 타입에 따라 NameOrId 의 타입이 IdLabel 또는 NameLabel로 결정됩니다.
이제 위의 장황한 오버로드 함수를 다음의 간결한 제네릭 함수로 표현할 수 있습니다.
function createLabel (idOrName: T): NameOrId  { throw "unimplemented"; } // 인자 타입이 string이므로 반환 타입은 `NameLabel`이 됨 let a = createLabel("typescript"); // 인자 타입이 number이므로 반환 타입은 `IdLabel`이 됨 let b = createLabel(2.8); // `random()`에 의해 인자 타입은 `string | number`가 되므로 반환 타입 역시 `NameLabel | IdLabel`이 됨 let c = createLabel(Math.random() ? "hello" : 42);
조건부 타입의 제약 조건
다음의 코드를 확인해 봅시다.
type MessageOf  = T["message"]; > Type '"message"' cannot be used to index type 'T'.
type MessageOf 는 T의 message 속성 타입을 취하고 있습니다. 하지만 제네릭 타입 T는 어떠한 extends도 가지지 않으므로 message를 알 수 없습니다. 그러므로 오류입니다.
이것을 다음처럼 표현하면 TypeScript는 더이상 오류를 표시하지 않습니다.
// T는 message 속성이 있고 그 타입은 알 수 없다는 것을 표현 type MessageOf  = T["message"]; interface Email { message: string; } // EmailMessageContents는 Email에 `message` 속성이 있으므로 그 타입인 `string`이 됨 type EmailMessageContents = MessageOf ;
하지만 해당 속성이 없을 경우 기본 값으로 naver 타입이 되게 할 수는 없을까요? 이때 조건부 타입을 사용할 수 있습니다.
// message 속성이 있을경우 참이므로 `T["message"]` 타입이 됨. 없을 경우 `never` 타입이 됨 type MessageOf  = T extends { message: unknown } ? T["message"] : never; interface Email { message: string; } interface Dog { bark(): void; } // string type EmailMessageContents = MessageOf ; // never type DogMessageContents = MessageOf ;
다름은 다른 예시 입니다.
type Flatten  = T extends any[] ? T[number] : T; // Extracts out the element type. type Str = Flatten ; // Leaves the type alone. type Num = Flatten ;
Flatten 은 any[]로 할당할 수 있으므로 참이 되어 T[number] 타입인 string이 됩니다. Flatten 의 경우 any[]로 할당할 수 없으므로 거짓이 되어 T 타입인 number가 됩니다.
조건부 유형 내 추론
Flatten  경우 처럼 타입을 추출하는 방식을 보았습니다. 이런 방법처럼 infer 키워드로 관심 있는 타입을 얻을 수 있습니다. 다음의 예시는 ReturnType 를 infer를 이용해서 구현한 것입니다.
// `infer Return`는 타입이 되고 그 타입은 `Return`으로 쓸 수 있음 // 즉, 함수의 반환형을 `Return`으로 취할 수 있게 됨 type GetReturnType  = Type extends (...args: never[]) => infer Return ? Return : never; // number type Num = GetReturnType  number>; // string type Str = GetReturnType  string>; // boolean[] type Bools = GetReturnType  boolean[]>
오버로드 함수의 다중 호출 서명의 경우 가장 포괄적인 서명을 선택합니다.
declare function stringOrNum(x: string): number; declare function stringOrNum(x: number): string; declare function stringOrNum(x: string | number): string | number; // string | number type T1 = ReturnType ;
다음의 경우를 살펴봅시다. 타입을 타입[]으로 확장하는 조건부 타입입니다.
type ToArray  = Type extends any ? Type[] : never;
유니온 타입을 사용하면 어떻게 될까요?
type ToArray  = Type extends any ? Type[] : never; // string[] | number[] type StrArrOrNumArr = ToArray ;
string | number는 ToArray  | ToArray 가 되어 결국에 string[] | number[]이 됩니다.
이 동작은 기본 동작입니다. 만약에 이 동작 대신 (string | number)[]이 되고자 한다면 다음처럼 가능 합니다.
type ToArrayNonDist  = [Type] extends [any] ? Type[] : never; // (string | number)[] type StrArrOrNumArr = ToArrayNonDist ;
Powered by Hashnode - a blogging community with over 2 Million members