interface StringContainer {
value: string
format(): string
split(): string[]
}
interface NumberContainer {
value: number
nearestPrime: number
round(): number
}
type Item<T> = {
id: T
container: any // 이러지 말자.
}목표는 T가 string이면 StringContainer, number 면 NumberContainer를 사용
첫번째 방법,
type Item<T> = {
id: T
container: T extends string ? StringContainer : NumberContainer
}
const item: Item<string> = {
id: 'abc',
container: null, // Type 'null' is not assignable to type StringContainer
}extends를 조건부 타입에서 사용한다면 ‘T 가 string 이면’ 와 같이 이해하면 좋다.
핸드북에 의하면,
T extends K ? A : B는 T가 K에 서브타입이면 A 아니면 B라고 설명한다.그 밖에도
extneds는 제약사항 을 주기 위해 자주 사용된다. 예시로
type MyReturnType<T extneds (...args: any) => any> = ...에서MyReturnType은 Generic으로 함수 타입만 받는다고 제약을 걸었다.
더 타입을 자세하게 준다면!
type Item<T> = {
id: T extends string | number ? T : never
container: T extends string
? StringContainer
: T extends number
? NumberContainer
: never
}
const item: Item<boolean> = {
id: true, // Type boolean is not assignable to type 'never',
container: null, // Type 'null' is not assignable to type 'never'
}위와 같이 never를 이용해서 string도 number 도 아니면 사용 불가능하게 구현할 수 있다.
never는 모든 타입의 sub type 이지만, 어떤 타입에도 할당 될 수 없다.
never의 또다른 이용 ArrayFilter
type ArrayFilter<T> = T extends any[] ? T : never
type StringsOrNumbers = ArrayFilter<string | number | string[] | number[]>여기서 T extends any[] 의 의미는 ‘T가 배열이면’과 같다. StringsOrNumbers 타입은 아래와 같은 과정으로 컴파일된다.
Generic을 이용해서 제약사항 걸기 (마치 never 처럼)
interface World {
getItem<T extends string | number>(id: T): T extends string ? Table : Dino
}
const what = world.getItem(true) // error!T는 string | number 인데 boolean을 넣었으므로 에러 발생.
Flatten
type Flatten<T> = T extends any[]
? T[number]
: T extends Object
? T[keyof T]
: T // primitive형우선 Array의 경우
const array = [1, 2, 'hi']
type ArrayFlattened = Flatten<typeof array>이 부분은 질문 해야하는 부분인데 array의 타입이 [number]와 함께 (number | string)[][number]로 추론되어 array의 각 인덱스를 참조한 값 즉 배열의 각 원소의 타입 number | string 으로 추론된다.
Object인 경우
const person = { name: 'Mark', age: 38 }
type ObjectFlattened = Flatten<typeof person>keyof는 Object의 프로퍼티를 열거한다. 즉 위 코드에서는 name | age 이다.
keyof T => 'name' | 'age'
T['name' | 'age'] => T['name'] | T['age'] => string | numberinfer
단어의 의미와 비슷하게 infer 를 이용하면 타입을 추론할 수 있다.
type UnpackPromise<T> = T extends Promise<infer K>[] ? K : any
const promises = [Promise.resolve('Mark'), Promise.resolve(38)]UnpackPromise 는 Generic으로 받은 타입을 infer 로 추론하여 T가 Promise의 Array라면 추론한 K로 타이핑하고 아니면 any로 타이핑한다. 위에서 promisese 의 타입은 (Promise<string> | Promise<number> )[] 이다.
type Expected = UnpackPromise<typeof promises>promises 를 unpack 하게 되면, infer에 의해 string 과 number 가 추론되고 K는 string | number 가 된다.
함수의 리턴 타입 알아내기 - MyReturnType
function plus(seed: number): number {
return seed + 1
}이 함수의 Return 타입은 딱 봐도 number 지만 어떤 함수든 리턴타입을 알아낼 수 있는 MyReturnType 타입을 만들어 보자.
type MyReturnType<T extends (...args: any) => any> = T extends (
...args: any
) => infer R
? R
: anyMyReturnType 은 Generic으로 함수만 받겠다고 제약사항을 걸었다. 그리고 함수를 받아서 infer 키워드를 통해 리턴값을 추론했다.
Excluded( diff )
type Excluded<T, U> = T extends U ? never : T
type A = Excluded<string | number, number>Excluded 는 T, U를 받아서 T가 U의 Sub Type 이라면 never 로 배제시킨다. (위에 ArrayFilter 만들 때 같은 논리) 따라서 A는 string 이다.
추가적으로, 이 코드에서 T는 유니온 타입을 받는다. 타입스크립트 컴파일러는 T가 유니온 타입이면 extends 키워드를 연산할 때 유니온 타입 각각에 대해서 연산한다. 즉 string extends U, number extends U 를 계산한다!
실제로 사용할 땐 Exclude<T, U> 를 사용하면 된다.
Extracted( filter )
type Extracted<T, U> = T extends U ? T : never
type B = Extracted<string | number, number> // numberExclude와 반대로 U에 맞지 않으면 타입을 filter 한다. 실제로 사용할 땐 Extract<T, U>를 사용하면 된다.
Pick
type Pick<T extends Object, U extends keyof T> = { [P in U]: T[P] }내가 원하는 Key를 갖는 Object의 타입을 알고 싶을 때 사용한다. 이 역시 이미 있는 헬퍼라서 그냥 Pick<T, U> 로 쓰면 된다.
Omit
원하지 않는 Key를 뺀 타입을 얻고 싶을 때 사용한다.
type Omit<T extends Object, U extends keyof T> = {
[P in Exclude<keyof T, U>]: T[P]
}Exclude 내장 헬퍼를 사용해서 U가 아닌 T의 key들만 P로 받아서 구하는 방법이다. 위에서 만들었던 Pick 을 활용하면 좀 더 간단해진다.
type Omit<T extends Object, U extends keyof T> = Pick<T, Exclude<keyof T, U>>이 역시 이미 있는 헬퍼라서 그냥 Omit<T, U> 로 쓰면 된다.
NonNullable
타입에 있는 null 이나 undefined 를 제거해준다.
type NonNullable<T> = T extends null | undefined ? never : TParameters
함수의 파라미터 타입을 튜플 형태로 반환한다.
type Parameters<(...args: any) => any> =
T extends (...args: infer P) => any ? P : neverGeneric의 제약사항에 함수만 받게 걸어놓고 infer 를 이용해서 파라미터를 추론했다.