🌱 ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Type-challenges] 18, 43, 189, 268, 533, 898, 3057, 3060, 3312, 2
    Front-End/TypeScript 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

    댓글

🍀 Y0ungZ dev blog.
스크롤 버튼