Front-End/TypeScript

[Type-challenges] 18, 43, 189, 268, 533, 898, 3057, 3060, 3312, 2

Y0ungZ 2024. 2. 23. 14:57
🍀 목차
18 - Length of Tuple
43 - Exclude
189 - Awaited
268 - If
533 - Concat
898 - Includes
3057 - Push
3060 - Unshift
3312 - Parameters
2 - Get Return Type

 

18 - Length of Tuple

// 배열(튜플)을 받아 길이를 반환하는 제네릭 Length<T>를 구현하세요.

// 예시
type tesla = ['tesla', 'model 3', 'model X', 'model Y']
type spaceX = ['FALCON 9', 'FALCON HEAVY', 'DRAGON', 'STARSHIP', 'HUMAN SPACEFLIGHT']

type teslaLength = Length<tesla>  // expected 4
type spaceXLength = Length<spaceX> // expected 5

 

/* _____________ 여기에 코드 입력 _____________ */

type Length<T> = any

/* _____________ 테스트 케이스 _____________ */
import type { Equal, Expect } from '@type-challenges/utils'

const tesla = ['tesla', 'model 3', 'model X', 'model Y'] as const
const spaceX = ['FALCON 9', 'FALCON HEAVY', 'DRAGON', 'STARSHIP', 'HUMAN SPACEFLIGHT'] as const

type cases = [
  Expect<Equal<Length<typeof tesla>, 4>>,
  Expect<Equal<Length<typeof spaceX>, 5>>,
  // @ts-expect-error
  Length<5>,
  // @ts-expect-error
  Length<'hello world'>,
]

 

type Length<T extends readonly any[]> = T['length'];

 

 readonly 키워드로 읽기 전용임을 나타낸 후, 안전하게 length속성을 추출할 수 있도록 한다. readonly를 사용하지 않으면 length 속성에 대한 접근을 허용하지 않고 오류를 발생시킨다. 

 

43 - Exclude

// T에서 U에 할당할 수 있는 타입을 제외하는 
// 내장 제네릭 Exclude<T, U>를 이를 사용하지 않고 구현하세요.

// 예시
type Result = MyExclude<'a' | 'b' | 'c', 'a'> // 'b' | 'c'

 

/* _____________ 여기에 코드 입력 _____________ */

type MyExclude<T, U> = any

/* _____________ 테스트 케이스 _____________ */
import type { Equal, Expect } from '@type-challenges/utils'

type cases = [
  Expect<Equal<MyExclude<'a' | 'b' | 'c', 'a'>, 'b' | 'c'>>,
  Expect<Equal<MyExclude<'a' | 'b' | 'c', 'a' | 'b'>, 'c'>>,
  Expect<Equal<MyExclude<string | number | (() => void), Function>, string | number>>,
]

 

type MyExclude<T, U> =  T extends U ? never : T;

 

 T에 오는 타입 중 U 타입은 제외하겠다고 정의하면 된다. never 타입은 값을 반환할 수 없는 타입이다. 에러를 던지거나, 무한 루프 등 정상적으로 종료되지 않을 때 사용된다. 또 다른 특징은 모든 타입의 하위 타입이란 것이다. never 자신을 제외한 어떤 타입도 never에 할당될 수 없다(any 조차도). 이 특징들은 TypeScript에서 다양한 패턴을 만들어 타입을 잡아주는 역할이 된다. 여기에서는 never을 반환시킴으로써 T에는 U에 할당 가능한 타입이 없게 한다. 결국, U를 제외시킨 T가 반환된다.  

 

 

189 - Awaited

// Promise와 같은 타입에 감싸인 타입이 있을 때, 
// 안에 감싸인 타입이 무엇인지 어떻게 알 수 있을까요?

//예시: Promise<ExampleType>이 있을 때, ExampleType을 어떻게 얻을 수 있을까요?

type ExampleType = Promise<string>

type Result = MyAwaited<ExampleType> // string
/* _____________ 여기에 코드 입력 _____________ */

type MyAwaited<T> = any

/* _____________ 테스트 케이스 _____________ */
import type { Equal, Expect } from '@type-challenges/utils'

type X = Promise<string>
type Y = Promise<{ field: number }>
type Z = Promise<Promise<string | number>>
type Z1 = Promise<Promise<Promise<string | boolean>>>
type T = { then: (onfulfilled: (arg: number) => any) => any }

type cases = [
  Expect<Equal<MyAwaited<X>, string>>,
  Expect<Equal<MyAwaited<Y>, { field: number }>>,
  Expect<Equal<MyAwaited<Z>, string | number>>,
  Expect<Equal<MyAwaited<Z1>, string | boolean>>,
  Expect<Equal<MyAwaited<T>, number>>,
]

// @ts-expect-error
type error = MyAwaited<number>

 

 테스트 케이스 타입 Z1처럼 중첩된 Promise의 타입도 알 수 있어야 한다(재귀적).

extends를 사용할 때 infer 키워드를 사용할 수 있다. extends로 조건을 서술하고 infer로 타입을 추론할 수 있다. 

 

type MyAwaited<T> = T extends Promise<infer U> ? U extends Promise<any> ? MyAwaited<U> : U : never;

 

 처음에는 위처럼 작성했다. 그런데 테스트 케이스 타입 T를 통과하지 못했다.

 

type T = { then: (onfulfilled: (arg: number) => any) => any }

 

 T 타입처럼 then만 구현된 유사 프로미스(?)는 프로미스가 정식적으로 채택되기 전 사용되었던 다양한 라이브러리가 가지던 형태라고 한다. TypeScript는 이를 지원하기 위해 좀 더 광범위한 프로미스인 PromiseLike를 추가하게 되었다.

 

type MyAwaited<T> = T extends PromiseLike<infer U> ? U extends PromiseLike<any> ? MyAwaited<U> : U : never;

 

-Like 타입에 대한 자세한 내용은 해당 포스팅을 읽었다. 

 

268- If

// 조건 C가 참일 때 반환하는 타입 T, 
// 거짓일 때 반환하는 타입 F를 받는 타입 If를 구현하세요. 
// C는 true 또는 false이고, T와 F는 아무 타입입니다.

// 예시
type A = If<true, 'a', 'b'>  // expected to be 'a'
type B = If<false, 'a', 'b'> // expected to be 'b'
/* _____________ 여기에 코드 입력 _____________ */

type If<C, T, F> = any

/* _____________ 테스트 케이스 _____________ */
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;

 

 

533 - Concat

// JavaScript의 Array.concat 함수를 타입 시스템에서 구현하세요.
// 타입은 두 인수를 받고, 인수를 왼쪽부터 concat한 새로운 배열을 반환해야 합니다.

// 예시
type Result = Concat<[1], [2]> // expected to be [1, 2]

 

/* _____________ 여기에 코드 입력 _____________ */

type Concat<T, U> = any

/* _____________ 테스트 케이스 _____________ */
import type { Equal, Expect } from '@type-challenges/utils'

const tuple = [1] as const

type cases = [
  Expect<Equal<Concat<[], []>, []>>,
  Expect<Equal<Concat<[], [1]>, [1]>>,
  Expect<Equal<Concat<typeof tuple, typeof tuple>, [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']>>,
]

// @ts-expect-error
type error = Concat<null, undefined>
type Concat<T extends readonly any[], U extends readonly any[]> =  [...T,...U];

 

898 - Includes

// JavaScript의 Array.includes 함수를 타입 시스템에서 구현하세요. 
// 타입은 두 인수를 받고, true 또는 false를 반환해야 합니다.

// 예시
type isPillarMen = Includes<['Kars', 'Esidisi', 'Wamuu', 'Santana'], 'Dio'> // expected to be `false`

 

/* _____________ 여기에 코드 입력 _____________ */

type Includes<T extends readonly any[], U> = any

/* _____________ 테스트 케이스 _____________ */
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>>,
]

 

type Includes<T extends readonly any[], U> = U extends T[number] ? true : false;

 

위처럼 작성하니 튜플 타입의 테스트케이스를 통과하지 못했다.

 

type Includes<T extends readonly any[], U> = T extends [infer First, ...infer Rest] ?
Equal<U, First> extends true ? true : Includes<Rest, U> : false;

 

 튜플이나 배열의 첫 번째 요소 타입을 First로 추론, 나머지 요소는 Rest로 추론한다. U가 배열이나 튜플의 첫 요소들과 같은지 비교한다. 일치하지 않다면 재귀적으로 Rest로 Includes 타입을 호출하여 확인해 나간다.

 

3057 - Push

// Array.push의 제네릭 버전을 구현하세요.

// 예시 
type Result = Push<[1, 2], '3'> // [1, 2, '3']

 

/* _____________ 여기에 코드 입력 _____________ */

type Push<T, U> = any

/* _____________ 테스트 케이스 _____________ */
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];

 

3060 - Unshift

// Array.unshift의 타입 버전을 구현하세요.

// 예시
type Result = Unshift<[1, 2], 0> // [0, 1, 2,]
/* _____________ 여기에 코드 입력 _____________ */

type Unshift<T, U> = any

/* _____________ 테스트 케이스 _____________ */
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];

 

 3057(Push) 문제와 3060(Unshift) 문제의 경우 any []가 아닌 unknown []을 쓴 분도 있어 차이를 알아보았다.

unknown 타입의 경우 any 타입과 유사하게 어떤 값이든 올 수 있음을 의미하는 동시에 "개발자에게 엄격한 타입 검사를 강제하는 의도"를 담고 있다. 타입 검사를 강제하고 타입이 식별된 후에 사용할 수 있기 때문에 any보다 안전하다. 예를 들어, any로 선언된 변수의 length 속성을 참조하면 에러가 나지 않지만 unknown 타입은 에러가 난다(더 안전하고 엄격하다).

// 안전하게 타입을 확인하고 싶다면 any보다는 unknown을 쓴다.
type Push<T extends unknown [], U> = [...T, U];
type Unshift<T extends unknown [], U> = [U, ...T];

 

3312 - Parameters

// 내장 제네릭 Parameters<T>를 이를 사용하지 않고 구현하세요.

// 예시
const foo = (arg1: string, arg2: number): void => {}

type FunctionParamsType = MyParameters<typeof foo> // [arg1: string, arg2: number]

 

/* _____________ 여기에 코드 입력 _____________ */

type MyParameters<T extends (...args: any[]) => any> = any

/* _____________ 테스트 케이스 _____________ */
import type { Equal, Expect } from '@type-challenges/utils'

function foo(arg1: string, arg2: number): void {}
function bar(arg1: boolean, arg2: { a: 'A' }): void {}
function 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> = T extends (...args : infer U) => unknown ? U : never;

 

 

2 - Get Return Type

// 내장 제네릭 ReturnType<T>을 이를 사용하지 않고 구현하세요.

// 예시
const fn = (v: boolean) => {
  if (v)
    return 1
  else
    return 2
}

type a = MyReturnType<typeof fn> // should be "1 | 2"

 

/* _____________ 여기에 코드 입력 _____________ */

type MyReturnType<T> = any

/* _____________ 테스트 케이스 _____________ */
import type { Equal, Expect } from '@type-challenges/utils'

type cases = [
  Expect<Equal<string, MyReturnType<() => string>>>,
  Expect<Equal<123, MyReturnType<() => 123>>>,
  Expect<Equal<ComplexObject, MyReturnType<() => ComplexObject>>>,
  Expect<Equal<Promise<boolean>, MyReturnType<() => Promise<boolean>>>>,
  Expect<Equal<() => 'foo', MyReturnType<() => () => 'foo'>>>,
  Expect<Equal<1 | 2, MyReturnType<typeof fn>>>,
  Expect<Equal<1 | 2, MyReturnType<typeof fn1>>>,
]

type ComplexObject = {
  a: [12, 'foo']
  bar: 'hello'
  prev(): number
}

const fn = (v: boolean) => v ? 1 : 2
const fn1 = (v: boolean, w: any) => v ? 1 : 2

 

 테스트 케이스 fn, fn1을 보면 매개변수가 존재하는 함수도 받아야 한다.

type MyReturnType<T> = T extends (...args : any[]) => infer U ? U : never;

 

 

 아래 코드처럼 anyunknown 타입으로 바꾸면 에러가 뜨는데, unknown []으로 argument를 설정하게 되면 함수 타입 T의 매개변수는 unknown이 올 것이라 예측하게 된다(당연하다). 그러나 fn은 number 타입의 매개변수를 받고 있다. unknown이 올 줄 알았는데 number 타입이 할당되었으므로 거짓이다. MyReturnType<typeof fn>은 never이 되는 것.

 

type MyReturnType<T> = T extends (...args : unknown []) => infer U ? U : never;

const fn = (v: number) => v ? 1 : 2;
type test = MyReturnType<typeof fn>;
// test는 never이다.

 

never로 추론된 test
never이 된다.

 

 

any에 대해서는 모든 타입을 허용하므로 컴파일러가 어떠한 유형의 값도 올바르다고 가정한다. 오류가 발생하지 않는다.

 

type MyReturnType<T> = T extends (...args : unknown []) => infer U ? U : never;

const fn = (v: any) => v ? 1 : 2;
type test = MyReturnType<typeof fn>;
// test는 1 | 2 이다.

 

오류가 뜨지 않는다.
오류가 뜨지 않음.

 

 

 만약, any [] 였다면?

T는 매개변수가 any 타입이 할당될 때만 참이 되고 number 타입의 매개변수가 할당되었으므로 참이 된다, any 타입은 모든 타입이 될 수 있기 때문이다. 

 

타입 계층 구조
타입 계층 구조

 

 

 any와 unknown 모두 어떤 값이든 할당 가능하나, unknown은 값 사용 전에 적절한 유형으로 바꿔주어야 한다. 그렇기에 타입스크립트 컴파일러는 명시적으로 값을 지정하거나 확인하라고 오류를 발생시킨다. 

 

 

 

참고자료

https://github.com/type-challenges/type-challenges/blob/main/questions/00018-easy-tuple-length/README.ko.md

https://github.com/type-challenges/type-challenges/blob/main/questions/00043-easy-exclude/README.ko.md

우아한 타입스크립트 with 리액트 - never 타입, infer를 활용해서 타입 추론하기, unknown 타입, 타입 

https://github.com/type-challenges/type-challenges/blob/main/questions/00189-easy-awaited/README.ko.md

https://yceffort.kr/2021/11/array-arraylike-promise-promiselike

https://github.com/type-challenges/type-challenges/blob/main/questions/00268-easy-if/README.ko.md

https://github.com/type-challenges/type-challenges/blob/main/questions/00533-easy-concat/README.ko.md

https://github.com/type-challenges/type-challenges/blob/main/questions/00898-easy-includes/README.ko.md

https://github.com/type-challenges/type-challenges/blob/main/questions/03057-easy-push/README.ko.md

https://github.com/type-challenges/type-challenges/blob/main/questions/03060-easy-unshift/README.ko.md

https://github.com/type-challenges/type-challenges/blob/main/questions/03312-easy-parameters/README.ko.md

https://github.com/type-challenges/type-challenges/blob/main/questions/00002-medium-return-type/README.ko.md