ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Type-challenges] 난이도 Medium - (6)
    Front-End(Web)/Typescript 2023. 2. 27. 04:00
    반응형

    Github 챌린지 문제들을 풀고 관련된 내용을 정리하면서, 부족했던 타입스크립트 기본지식을 다지고자 한다. (주 1-2회)

     

    https://github.com/type-challenges/type-challenges

     

    GitHub - type-challenges/type-challenges: Collection of TypeScript type challenges with online judge

    Collection of TypeScript type challenges with online judge - GitHub - type-challenges/type-challenges: Collection of TypeScript type challenges with online judge

    github.com

     


    📘 목차 - Medium

    1. RequiredByKeys
    2. Mutable
    3. OmitByType
    4. ObjectEntries
    5. Shift
    6. Tuple to Nested Object

     

     

    📘 문제 및 풀이

     

    1. RequiredByKeys

    Implement a generic RequiredByKeys<T, K> which takes two type argument T and K.
    K specify the set of properties of T that should set to be required. When K is not provided, it should make all properties required just like the normal Required<T>.

    For example
    interface User {
      name?: string
      age?: number
      address?: string
    }
    
    type UserRequiredName = RequiredByKeys<User, 'name'> // { name: string; age?: number; address?: string }
    import type { Equal, Expect } from '@type-challenges/utils'
    
    interface User {
      name?: string
      age?: number
      address?: string
    }
    
    interface UserRequiredName {
      name: string
      age?: number
      address?: string
    }
    
    interface UserRequiredNameAndAge {
      name: string
      age: number
      address?: string
    }
    
    type cases = [
      Expect<Equal<RequiredByKeys<User, 'name'>, UserRequiredName>>,
      Expect<Equal<RequiredByKeys<User, 'name' | 'age'>, UserRequiredNameAndAge>>,
      Expect<Equal<RequiredByKeys<User>, Required<User>>>,
      // @ts-expect-error
      Expect<Equal<RequiredByKeys<User, 'name' | 'unknown'>, UserRequiredName>>,
    ]

     

    🖌 풀이

    type Merge<T> = {
      [P in keyof T]: T[P]
    }
    
    type RequiredByKeys<T, K extends keyof T = keyof T> = Merge<{
      [P in keyof T as P extends K ? P : never]-?: T[P]
    } & {
      [P in keyof T as P extends K ? never : P]: T[P]
    }>

    지난 풀이의 7. PartialByKeys 를 응용한 풀이이다. (링크)

     

    [Type-challenges] 난이도 Medium - (5)

    Github 챌린지 문제들을 풀고 관련된 내용을 정리하면서, 부족했던 타입스크립트 기본지식을 다지고자 한다. (주 1-2회) https://github.com/type-challenges/type-challenges GitHub - type-challenges/type-challenges: Collect

    abangpa1ace.tistory.com

    단, Optional한 키값을 Required로 바꿔주기 위해 -? 문법을 사용했다.

     

    * Mapping Modifiers

     

    Mapped Type을 만들 때, 프로퍼티에 +와 - 연산자를 사용할 수 있다.

    이는, ?(optional), readonly 등의 조건을 추가할지 말지 여부로, 아무것도 기입하지 않으면 +이므로 -는 이에 반대로 적용한다는 의미이다. (참고링크)


    2. Mutable

    Implement the generic Mutable<T> which makes all properties in T mutable (not readonly).

    For example
    interface Todo {
      readonly title: string
      readonly description: string
      readonly completed: boolean
    }
    
    type MutableTodo = Mutable<Todo> // { title: string; description: string; completed: boolean; }
    /* _____________ Test Cases _____________ */
    import type { Equal, Expect } from '@type-challenges/utils'
    
    interface Todo1 {
      title: string
      description: string
      completed: boolean
      meta: {
        author: string
      }
    }
    
    type List = [1, 2, 3]
    
    type cases = [
      Expect<Equal<Mutable<Readonly<Todo1>>, Todo1>>,
      Expect<Equal<Mutable<Readonly<List>>, List>>,
    ]
    
    type errors = [
      // @ts-expect-error
      Mutable<'string'>,
      // @ts-expect-error
      Mutable<0>,
    ]

     

    🖌 풀이

    type Mutable<T extends object> = {
      -readonly [P in keyof T]: T[P]
    }

    위의 Mapping Modifiers 를 활용한 풀이이다. 단, 에러 케이스처럼 객체 외 값이 들어오는 경우를 예외처리해준다.

     


    3. OmitByType

    From T, pick a set of properties whose type are not assignable to U.

    For Example
    type OmitBoolean = OmitByType<{
      name: string
      count: number
      isReadonly: boolean
      isEnable: boolean
    }, boolean> // { name: string; count: number }
    /* _____________ Test Cases _____________ */
    import type { Equal, Expect } from '@type-challenges/utils'
    
    interface Model {
      name: string
      count: number
      isReadonly: boolean
      isEnable: boolean
    }
    
    type cases = [
      Expect<Equal<OmitByType<Model, boolean>, { name: string; count: number }>>,
      Expect<Equal<OmitByType<Model, string>, { count: number; isReadonly: boolean; isEnable: boolean }>>,
      Expect<Equal<OmitByType<Model, number>, { name: string; isReadonly: boolean; isEnable: boolean }>>,
    ]

     

    🖌 풀이

    type OmitByType<T, U> = {
      [P in keyof T as T[P] extends U ? never : P]: T[P]
    }

    마찬가지로, 지난 풀이의 4. PickByType을 응용하면 된다. (링크)

     

    [Type-challenges] 난이도 Medium - (5)

    Github 챌린지 문제들을 풀고 관련된 내용을 정리하면서, 부족했던 타입스크립트 기본지식을 다지고자 한다. (주 1-2회) https://github.com/type-challenges/type-challenges GitHub - type-challenges/type-challenges: Collect

    abangpa1ace.tistory.com

     


    4. ObjectEntries

    Implement the type version of Object.entries
    For example
    interface Model {
      name: string;
      age: number;
      locations: string[] | null;
    }
    type modelEntries = ObjectEntries<Model> // ['name', string] | ['age', number] | ['locations', string[] | null];
    /* _____________ Test Cases _____________ */
    import type { Equal, Expect } from '@type-challenges/utils'
    
    interface Model {
      name: string
      age: number
      locations: string[] | null
    }
    
    type ModelEntries = ['name', string] | ['age', number] | ['locations', string[] | null]
    
    type cases = [
      Expect<Equal<ObjectEntries<Model>, ModelEntries>>,
      Expect<Equal<ObjectEntries<Partial<Model>>, ModelEntries>>,
      Expect<Equal<ObjectEntries<{ key?: undefined }>, ['key', undefined]>>,
      Expect<Equal<ObjectEntries<{ key: undefined }>, ['key', undefined]>>,
    ]

     

    🖌 풀이

    type ObjectEntries<T> = {
      [P in keyof T]-?: [P, T[P] extends infer R | undefined ? R : T[P]];
    }[keyof T];

    먼저 Mapping Modifiers로 옵셔널 키값들을 제외해줬다.

    그리고 Partial의 케이스에서, value가 옵셔널하게 undefined인 경우도 정상적으로 반영되도록 infer R | undefined 인 경우엔 R 타입만 빼내서 반영해준 것이다.

     


    5. Shift

    Implement the type version of Array.shift

    For example
    type Result = Shift<[3, 2, 1]> // [2, 1]
    /* _____________ Test Cases _____________ */
    import type { Equal, Expect } from '@type-challenges/utils'
    
    type cases = [
      // @ts-expect-error
      Shift<unknown>,
      Expect<Equal<Shift<[]>, []>>,
      Expect<Equal<Shift<[1]>, []>>,
      Expect<Equal<Shift<[3, 2, 1]>, [2, 1]>>,
      Expect<Equal<Shift<['a', 'b', 'c', 'd']>, ['b', 'c', 'd']>>,
    ]

     

    🖌 풀이

    type Shift<T extends any[]> = T extends [infer F, ...infer R] ? R : [];

    지난 풀이의 1. Pop 문제보다 좀 더 쉽다. 배열의 나머지(R) 부분을 반환해주면 된다. 에러 케이스를 고려해 T를 배열로 지정한다. (링크)

     

    [Type-challenges] 난이도 Medium - (1)

    Github 챌린지 문제들을 풀고 관련된 내용을 정리하면서, 부족했던 타입스크립트 기본지식을 다지고자 한다. (주 1-2회) https://github.com/type-challenges/type-challenges GitHub - type-challenges/type-challenges: Collect

    abangpa1ace.tistory.com

     


    6. Tuple to Nested Object

    Given a tuple type T that only contains string type, and a type U, build an object recursively.
    type a = TupleToNestedObject<['a'], string> // {a: string}
    type b = TupleToNestedObject<['a', 'b'], number> // {a: {b: number}}
    type c = TupleToNestedObject<[], boolean> // boolean. if the tuple is empty, just return the U type
    /* _____________ Test Cases _____________ */
    import type { Equal, Expect } from '@type-challenges/utils'
    
    type cases = [
      Expect<Equal<TupleToNestedObject<['a'], string>, { a: string }>>,
      Expect<Equal<TupleToNestedObject<['a', 'b'], number>, { a: { b: number } }>>,
      Expect<Equal<TupleToNestedObject<['a', 'b', 'c'], boolean>, { a: { b: { c: boolean } } }>>,
      Expect<Equal<TupleToNestedObject<[], boolean>, boolean>>,
    ]

     

    🖌 풀이

    type TupleToNestedObject<T, U> = T extends [infer F extends PropertyKey, ...infer R] ? { [P in F]: TupleToNestedObject<R, U> } : U

    배열의 분리와, 첫 값인 F를 키값으로 해서 유틸리티를 재귀적으로 돌려 객체를 만들면 된다.

    F를 PropertyKey로 지정한 이유는, 이를 처리하지 않으면 키값 타입(string, number, symbol)에 부합하지 않을 수 있다는 에러가 발생하기 때문이다. 

     

     

    * PropertyKey

     

    Javascript 객체의 키 값으로 사용가능한 타입인 string, number, symbol의 유니온 타입이다. (참고링크)

     

    반응형
Designed by Tistory.