type MessageOf<T> = T["message"];
// Type Error: Type '"message"' cannot be used to index type 'T'.
type MessageOf<T extends { message: unknown }> = T["message"];
type Email = { message: string; }
type Chat = { text: string; }
type EmailMessage = MessageOf<Email>
type Email = MessageOf<Chat> // Type Error
위의 예시에서 첫번째 MessageOf 을 확인해 봅시다. 제너릭 T 의 타입은 정해지지 않았습니다. 따라서, property 에 message 가 없을 수 도 있기 때문에 에러가 발생 합니다.
두번째 MessageOf 같은 경우는 extends 로 타입을 제한합니다. message property 를 가진 타입만 제너릭으로 들어올 수 있으므로, Error 가 발생하지 않습니다.
반대로 제너릭을 사용할 때 옳지 않은 타입을 넘겨주면 Type Error 가 발생합니다.
조건부 타입
extends 는 많은 언어가 포함하고 있는 if 의 역할을 하기도 합니다.
type Example = "A" extends string ? "STRING" : "UNKNOWN"; // "STRING"
// 제너릭에서 사용이 가능합니다.
type MessageOf<T> = T extends { message: unknown } ? T["message"] : never;
interface Email { message: string; }
interface Dog { bark(): void; }
type EmailMessageContents = MessageOf<Email>; // string
type DogMessageContents = MessageOf<Dog>; // never
// 중첩도 가능 합니다.
type TypeOf<T> = T extends string
? 'string'
: T extends number
? 'number'
: T extends boolean
? 'boolean'
: 'unknown'
type StrType = TypeOf<string> // "string"
type StrType = TypeOf<number> // "string"
type StrType = TypeOf<Function> // "unknown"
타입 추론 Infer
아래와 같은 타입이 있다고 가정해봅시다. 우리는 때로 'User' 타입이 필요할 수 있습니다. 그럴땐 infer 키워드를 통해 타입을 추론 할 수 있습니다.
const userList = [{ name: 'John' }] // type: Array<User>
type ItemOf<T> = T extends Array<infer U> ? U : unknown;
type User = ItemOf<typeof userList>; // User { name: string }
위에서 infer U 는 추론하고자 하는 타입을 의미합니다. 위의 ItemOf 를 문맥 그대로 해석해 봅시다.
제너릭 T 가 Array<추론하고자 하는 타입> 타입의 형태를 하고 있다면 ItemOf 의 반환 타입은 추론하고자 하는 타입이고, 아니면 unknown 이 라고 해석할 수 있습니다.