JavaScript의 Array.includes 함수를 타입 시스템에서 구현하세요. 타입은 두 인수를 받고, true 또는 false를 반환해야 합니다.
type Includes<T extends readonly any[], U> =
T extends [infer Head,...infer Rest]?
(Equal<Head,U> extends true?
(true)
:(Includes<Rest,U>))
:(false)
재귀를 활용한 기본적인 배열 순환 방식으로 접근했다.
Includes<[{ readonly a: 'A' }], { a: 'A' }>와 같이 비슷하거나
Includes<[1 | 2], 1>와 같이 대입가능한 값도 false가 나와야해서
type-challenge에서 제공하는 Equal 타입을 통해 같은지 아닌지 판단했다.
type Includes<T extends readonly any[], U> = {
[P in T[number]]: true
}[U] extends true ? true : false;
이 방법은 mapped type을 활용한 방법이지만, 제네릭으로 들어오는 배열의 타입이 string,number,symbol이 아닌 경우 탐지가 불가능 했다
type Includes<T extends readonly any[], U> = T extends [infer L, ...infer R]
? [U, L] extends [L, U]
? true
: Includes<R, U>
: false
이 방법도 비교하는 타입과 비교당하는 타입이 서로 상속될 때만 허용이 되기 때문에
{ readonly a: 'A' }와 { a: 'A'} 같은 타입은 탐지 할 수 없었다.
다만, conditional type 을 두 번 쓸 여지를 줄여주었다는 점이 핵심이다
Equal 타입은 템플릿 같은 느낌으로 사용하기 때문에, 원리를 이해하기보다는 그냥 사용하려고 한다
Equal 타입의 구현체는 다음과 같다
type Equal<X, Y> =
(<T>() => T extends X ? 1 : 2) extends
<T>() => T extends Y ? 1 : 2 ?
true
: false
자세한 원리까지는 이해할 수 없지만, 함수의 반환 타입을 엄격하게 비교하는 동안에 정밀한 검사가 이뤄져 서로 완벽히 같은 타입인지를 판단하는 타입인 것으로 보인다.
내가 잘 설명할 수 있는 부분은 배열의 spread와 infer을 활용한 배열 판단이다.
T extends [infer Head,...infer Rest]는
false 조건이 만족되고, 나머지는 true조건이 만족된다. Head에는 0번째 요소의 타입, Rest에는 빈 배열이 할당된다Head에는 0번째 요소의 타입, Rest에는 1번째 배열이후의 값들이(js의 array.slice(1)과 유사하다고 생각하면 쉽다) 할당된다이를 통해 Head에 들어간 값이 U와 같으면 true를 반환하고, 그렇지 않다면 Includes<Rest,U>의 값을 확인한다.
빈 배열일 경우 무조건 false를 반환한다
타입 챌린지에서는 이와 같은 배열 순환 방식을 이해하면 대다수의 보통 난이도 문제를 풀 수 있게 된다.