View on GitHub: https://tsch.js.org/189
Promise
와 같은 타입에 감싸인 타입이 있을 때, 안에 감싸인 타입이 무엇인지 어떻게 알 수 있을까요?
예시: Promise<ExampleType>
이 있을 때, ExampleType
을 어떻게 얻을 수 있을까요?
type MyAwaited<T> = T extends PromiseLike<infer U> ? MyAwaited<U> : T;
T
가 PromiseLike
라면 infer
를 사용하여 내부 타입 U
를 추출하고, 재귀적으로 MyAwaited<U>
를 호출T
가 PromiseLike
가 아니라면 최종적으로 T
를 반환왜
PromiseLike
를 사용하는가?
PromiseLike
는 Promise
와 비슷한 타입을 나타내는 인터페이스PromiseLike
는 then
메서드만 지원, catch
나 finally
메서드는 지원하지 않음{ then: (onfulfilled: (arg: number) => any) => any }
를 처리하기 위해 Promise
가 아닌 PromiseLike
를 사용 (Promise가 성립하려면 catch
와 finally
가 있어야 함)interface Promise<T> {
then<TResult1 = T, TResult2 = never>(
onfulfilled?: (value: T) => TResult1 | PromiseLike<TResult1>,
onrejected?: (reason: any) => TResult2 | PromiseLike<TResult2>
): Promise<TResult1 | TResult2>;
catch<TResult = never>(
onrejected?: (reason: any) => TResult | PromiseLike<TResult>
): Promise<T | TResult>;
finally(onfinally?: (() => void) | null): Promise<T>;
}
interface PromiseLike<T> {
then<TResult1 = T, TResult2 = never>(
onfulfilled?: (value: T) => TResult1 | PromiseLike<TResult1>,
onrejected?: (reason: any) => TResult2 | PromiseLike<TResult2>
): PromiseLike<TResult1 | TResult2>;
}
infer
란 무엇인가?
infer
키워드는 조건부 타입에 사용되며, 조건식이 참으로 평가될 때 사용할 수 있는 타입을 추론하는 데 필요한 도구
infer
키워드는 컨디셔널 타입과 함께 사용되어야 함
컨디셔널 타입에서 추론하고자 하는 타입에 infer
키워드를 사용하여 추론
infer
를 활용해 배열의 요소 타입, Promise의 결과 타입, 함수의 반환 타입을 추출할 수 있다.
// 배열의 요소 타입 추출
type ElementType<T> = T extends (infer U)[] ? U : T;
type Test1 = ElementType<number[]>; // number
type Test2 = ElementType<string[]>; // string
type Test3 = ElementType<boolean>; // boolean (배열이 아니므로 그대로 반환)
// Promise의 결과 타입 추출
type AwaitedType<T> = T extends Promise<infer U> ? U : T;
type Test1 = AwaitedType<Promise<string>>; // string
type Test2 = AwaitedType<Promise<number>>; // number
type Test3 = AwaitedType<number>; // number (Promise가 아니므로 그대로 반환)
// 함수의 반환 타입 추출
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
type Test1 = ReturnType<() => string>; // string
type Test2 = ReturnType<(x: number) => void>; // void
type Test3 = ReturnType<number>; // never (함수가 아니므로)
View on GitHub: https://tsch.js.org/268
조건 C
, 참일 때 반환하는 타입 T
, 거짓일 때 반환하는 타입 F
를 받는 타입 If
를 구현하세요. C
는 true
또는 false
이고, T
와 F
는 아무 타입입니다.
type If<C extends boolean, T, F> = C extends true ? T : F;
C extends true ? T : F
는 C
가 true
일 때 T
를 반환하고, false
일 때 F
를 반환C extends true
에서 extends
는 조건부 타입 정의에 사용됨. 삼항 연산자와 비슷한 역할.조건부 타입에서 extends는 모든 타입을 받을 수 있을까?
extends
는 모든 타입을 받을 수 있음type Example<T> = T extends string ? true : false;
type Result = Example<"a" | 1>; // true | false
true
를 반환 (Intersection Type 포함)// Subtype Relation
type IsSubType = { a: string } extends { a: string; b?: number } ? true : false; // true
// Intersection Type
type Intersection<T> = T extends { a: string } & { b: number } ? true : false;
type Result = Intersection<{ a: string; b: number }>; // true
any
타입은 모든 타입의 서브타입이므로 true
를 반환type IsAny<T> = T extends any ? true : false;
type Result = IsAny<any>; // true
never
타입은 평가되지 않음(never
를 반환)type IsNever<T> = T extends never ? true : false;
type Result = IsNever<never>; // never
type IsArray<T> = T extends any[] ? true : false;
type Result1 = IsArray<number[]>; // true
type Result2 = IsArray<number>; // false
View on GitHub: https://tsch.js.org/533
JavaScript의 Array.concat
함수를 타입 시스템에서 구현하세요. 타입은 두 인수를 받고, 인수를 왼쪽부터 concat
한 새로운 배열을 반환해야 합니다.
type Concat<T extends readonly any[], U extends readonly any[]> = [...T, ...U];
readonly any[]
를 사용View on GitHub: https://tsch.js.org/898
JavaScript의 Array.includes
함수를 타입 시스템에서 구현하세요. 타입은 두 인수를 받고, true
또는 false
를 반환해야 합니다.
type MyEqual<X, Y> =
(<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y ? 1 : 2
? true
: false;
type Includes<T extends readonly any[], U> = T extends [
infer First,
...infer Rest,
]
? MyEqual<First, U> extends true
? true
: Includes<Rest, U>
: false;
MyEqual
타입은 두 타입이 같은지 확인하는 타입. 두 타입이 같으면 true
, 다르면 false
를 반환<T>() => T extends X ? 1 : 2
타입은 타입 T
가 X
의 서브타입이면 1
, 아니면 2
를 반환하는 타입T extends [infer First, ...infer Rest]
타입은 타입 T
가 배열이면 First
는 첫 번째 요소, Rest
는 나머지 요소들을 반환First
와 U
를 비교하여 한 번이라도 같으면 true
, 다르면 false
를 반환
<T>()
는 무엇인가?
T
는 함수 내부에서 사용할 수 있는 제네릭 타입 매개변수<A>
), 제네릭 타입 매개변수라는 것을 명시해야 함
MyEqual
에 대한 이해
MyEqual
은 두 타입이 같은지 확인하는 타입(<T>() => T extends X ? 1 : 2)
는 T
가 X
의 서브타입이면 1
, 아니면 2
를 반환하는 타입(<T>() => T extends Y ? 1 : 2)
는 T
가 Y
의 서브타입이면 1
, 아니면 2
를 반환하는 타입구조적 동등성
을 사용하여 비교 (JavaScript에서는 함수 비교를 시행할 때, 참조적 동등성
을 사용하여 비교)MyEqual
은 두 함수의 매개변수와 반환 타입이 같으면 true
, 다르면 false
를 반환하는 타입type A = <T>() => T extends string ? 1 : 2;
type B = <T>() => T extends string ? 1 : 2;
type AreEqual = A extends B ? true : false;
// A와 B의 함수 타입 구조가 동일하므로, A extends B는 true로 평가됨
type A = <T>() => T extends string ? 1 : 2;
type B = <T>() => T extends number ? 1 : 2;
type AreEqual = A extends B ? true : false;
// A와 B의 함수 타입 구조가 다르므로, A extends B는 false로 평가됨
type A = <T>() => T extends string ? 1 : 2;
type B = <T>() => T extends string ? 1 : 3;
type AreEqual = A extends B ? true : false;
// A와 B의 함수 타입 구조가 다르므로, A extends B는 false로 평가됨
View on GitHub: https://tsch.js.org/3058
Array.push
의 제네릭 버전을 구현하세요.
type Push<T extends any[], U> = [...T, U];
[...T, U]
는 타입 T
의 배열에 타입 U
를 추가한 새로운 배열을 반환...
는 스프레드 연산자로, 배열을 풀어서 각 요소를 별도의 인수로 전달T extends any[]
는 타입 T
가 배열이어야 함을 제약U
는 추가할 요소의 타입View on GitHub: https://tsch.js.org/3060
Array.unshift
의 제네릭 버전을 구현하세요.
type Unshift<T extends any[], U> = [U, ...T];
[U, ...T]
는 타입 U
를 첫 번째 요소로 하고, 타입 T
의 배열을 나머지 요소로 하는 새로운 배열을 반환...
는 스프레드 연산자로, 배열을 풀어서 각 요소를 별도의 인수로 전달T extends any[]
는 타입 T
가 배열이어야 함을 제약U
는 추가할 요소의 타입View on GitHub: https://tsch.js.org/3312
내장 제네릭 Parameters<T>
를 이를 사용하지 않고 구현하세요.
type MyParameters<T extends (...args: any[]) => any> = T extends (
...args: infer U
) => any
? U
: never;
T
가 (...args: any[]) => any
형태(함수 타입)인지 확인args
부분(= 함수의 파라미터들)을 infer U
라고 두어 '함수 매개변수들의 튜플 타입'을 추론U
를 반환never
반환...args
는 아무 이름이어도 되고, TypeScript에서 그냥 ‘추론할 위치’가 rest parameter
임을 표시하기 위한 placeholder