[Type-challenges] 난이도 Easy - (2)
Github 챌린지 문제들을 풀고 관련된 내용을 정리하면서, 부족했던 타입스크립트 기본지식을 다지고자 한다. (주 1-2회)
https://github.com/type-challenges/type-challenges
📘 목차 - Easy
- If
- Concat
- Includes
- Push
- Unshift
- Parameters
📘 문제 및 풀이
1. If
Implement the util type If<C, T, F> which accepts condition C, a truthy value T, and a falsy value F. C is expected to be either true or false while T and F can be any type.
For example:
type A = If<true, 'a', 'b'> // expected to be 'a'
type B = If<false, 'a', 'b'> // expected to be 'b'
/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
type cases = [
Expect<Equal<If<true, 'a', 'b'>, 'a'>>,
Expect<Equal<If<false, 'a', 2>, 2>>,
]
// @ts-expect-error
type error = If<null, 'a', 'b'>
🖌 풀이
type If<C extends boolean, T, F> = C extends true ? T : F;
If 유틸리티 타입을 직접 작성하는 코드다. C가 true면 T 아니면 F 타입을 조건부로 반환해준다.
단, 에러 케이스처럼 C가 Boolean 타입임을 제약해서, 이외의 경우엔 에러를 발생시킨다.
2. Concat
Implement the JavaScript Array.concat function in the type system. A type takes the two arguments. The output should be a new array that includes inputs in ltr order
For example:
type Result = Concat<[1], [2]> // expected to be [1, 2]
/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
type cases = [
Expect<Equal<Concat<[], []>, []>>,
Expect<Equal<Concat<[], [1]>, [1]>>,
Expect<Equal<Concat<[1, 2], [3, 4]>, [1, 2, 3, 4]>>,
Expect<Equal<Concat<['1', 2, '3'], [false, boolean, '4']>, ['1', 2, '3', false, boolean, '4']>>,
]
🖌 풀이
type Concat<T extends any[], U extends any[]> = [...T, ...U]
T, U 타입이 각각 배열임을 명시해주고, 이를 spread한 배열(타입)을 생성하면 된다.
3. Includes
Implement the JavaScript Array.includes function in the type system. A type takes the two arguments. The output should be a boolean true or false.
For example:
type isPillarMen = Includes<['Kars', 'Esidisi', 'Wamuu', 'Santana'], 'Dio'> // expected to be `false`
/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
type cases = [
Expect<Equal<Includes<['Kars', 'Esidisi', 'Wamuu', 'Santana'], 'Kars'>, true>>,
Expect<Equal<Includes<['Kars', 'Esidisi', 'Wamuu', 'Santana'], 'Dio'>, false>>,
Expect<Equal<Includes<[1, 2, 3, 5, 6, 7], 7>, true>>,
Expect<Equal<Includes<[1, 2, 3, 5, 6, 7], 4>, false>>,
Expect<Equal<Includes<[1, 2, 3], 2>, true>>,
Expect<Equal<Includes<[1, 2, 3], 1>, true>>,
Expect<Equal<Includes<[{}], { a: 'A' }>, false>>,
Expect<Equal<Includes<[boolean, 2, 3, 5, 6, 7], false>, false>>,
Expect<Equal<Includes<[true, 2, 3, 5, 6, 7], boolean>, false>>,
Expect<Equal<Includes<[false, 2, 3, 5, 6, 7], false>, true>>,
Expect<Equal<Includes<[{ a: 'A' }], { readonly a: 'A' }>, false>>,
Expect<Equal<Includes<[{ readonly a: 'A' }], { a: 'A' }>, false>>,
Expect<Equal<Includes<[1], 1 | 2>, false>>,
Expect<Equal<Includes<[1 | 2], 1>, false>>,
Expect<Equal<Includes<[null], undefined>, false>>,
Expect<Equal<Includes<[undefined], null>, false>>,
]
🖌 풀이
1) infer 추론 + 재귀를 활용한 풀이
type Includes<T extends readonly any[], U> = T extends [infer First, ...infer Rest]
? MyEqual<First, U> extends true
? true
: Includes<Rest, U>
: false
type MyEqual<X, Y> = (<T>() => T extends X ? 1 : 2) extends (<T>() => T extends Y ? 1 : 2)
? true
: false
먼저, MyEqual 이라는 타입으로 Equal 유틸리티 타입을 직접 구현했다. 두 값이 같으면 true, 아니면 false 를 반환한다.
이제, Includes 타입은 T의 엘리먼트들에 대해 재귀적으로 MyEqual을 돌리면서 U와 비교한다.
이 때, infer를 통해 T배열의 첫 번째 인자와 나머지 인자들을 추론하게끔 한다.
2) 배열을 객체화한 풀이
type Includes<T extends readonly any[], U> = {
[t in keyof T]: Equal<T[t], U>
}[number] extends false ? false : true
t는 T 배열타입의 인덱스가 된다.(in keyof)
이 인덱스(t)를 key로, T[t] 엘리먼트와 U의 Equal 여부를 value로 하는 객체 타입을 만들고,
이것의 [number]로 각 키를 순회 해서 Equal 여부를 반환해주면 된다.
* in keyof
in 키워드는 index signatures 로 정의된다. 보통, 유니온 타입에 해당 타입이 포함되는지 여부를 연산하기에 Type Guard에 쓰인다.
interface A {
x: number;
}
interface B {
y: string;
}
let q: A | B = ...;
if ('x' in q) {
// q: A
} else {
// q: B
}
in 키워드가 keyof와 결합되면, 배열에서의 mapped type definition 으로도 정의되며이는 배열의 각 인자를 다시 맵핑할 수 있다.
interface Person {
name: string;
age: number;
}
type Partial<T> = {
[P in keyof T]?: T[P]; // P will be each key of T
}
type PersonPartial = Partial<Person>; // same as { name?: string; age?: number; }
(참고링크)
4. Push
Implement the generic version of Array.push
For example:
type Result = Push<[1, 2], '3'> // [1, 2, '3']
/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
type cases = [
Expect<Equal<Push<[], 1>, [1]>>,
Expect<Equal<Push<[1, 2], '3'>, [1, 2, '3']>>,
Expect<Equal<Push<['1', 2, '3'], boolean>, ['1', 2, '3', boolean]>>,
]
🖌 풀이
type Push<T extends any[], U> = [...T, U]
위 2번의 Concat의 풀이를 참고했다. T가 배열임을 extends로 명시한 다음 spread 하고, 거기에 U를 추가한 타입이다.
5. Unshift
Implement the type version of Array.unshift
For example:
type Result = Unshift<[1, 2], 0> // [0, 1, 2,]
/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
type cases = [
Expect<Equal<Unshift<[], 1>, [1]>>,
Expect<Equal<Unshift<[1, 2], 0>, [0, 1, 2]>>,
Expect<Equal<Unshift<['1', 2, '3'], boolean>, [boolean, '1', 2, '3']>>,
]
🖌 풀이
type Unshift<T extends any[], U> = [U, ...T]
위 4번의 Push와 아주 유사한 풀이이다. Unshift는 인자를 앞에 추가하므로, U와 spread 순서만 바꿔주면 된다.
6. Parameters
Implement the built-in Parameters generic without using it.
For example:
const foo = (arg1: string, arg2: number): void => {}
type FunctionParamsType = MyParameters<typeof foo> // [arg1: string, arg2: number]
/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
const foo = (arg1: string, arg2: number): void => {}
const bar = (arg1: boolean, arg2: { a: 'A' }): void => {}
const baz = (): void => {}
type cases = [
Expect<Equal<MyParameters<typeof foo>, [string, number]>>,
Expect<Equal<MyParameters<typeof bar>, [boolean, { a: 'A' }]>>,
Expect<Equal<MyParameters<typeof baz>, []>>,
]
🖌 풀이
type MyParameters<T extends (...args: any[]) => any> =
T extends (...args: infer P) => any ? P : never;
Parameters 유틸리티 타입을 직접 구현하는 풀이다.
T가 함수타입이 맞다면, infer로 추론한 인자들의 타입(배열)인 P를 반환하고 아니면 never(혹은 false)를 반환한다.