🌱 ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Type-challenges] 13, 4, 7, 11, 14
    Front-End/TypeScript 2024. 2. 16. 11:57
    🍀 목차
    13 - Hello World
    4 - Pick
    7 - Readonly
    11 - Tuple to Object
    14 - First of Array

     

     

    13 - Hello World

    // string이 되어야 합니다.
    type HelloWorld = any
    
    // 아래의 테스트가 통과하도록 만드세요.
    type test = Expect<Equal<HelloWorld, string>>

     

    /* _____________ 여기에 코드 입력 _____________ */
    
    type HelloWorld = any // expected to be a string
    
    /* _____________ 테스트 케이스 _____________ */
    import type { Equal, Expect, NotAny } from '@type-challenges/utils'
    
    type cases = [
      Expect<NotAny<HelloWorld>>,
      Expect<Equal<HelloWorld, string>>,
    ]

     

     테스트 케이스를 보면  HelloWorld는 any여서는 안 되고(NotAny), HelloWorld와 string은 타입이 동일해야 한다(Equal). 하지만 HelloWorld가 any로 선언되었기에 에러가 뜬다. 

     

    HelloWorld의 type을 string 타입으로 선언해 주면 된다. 

     

    type HelloWorld = string

     

     

    4 - Pick

    // T에서 K 프로퍼티만 선택해 새로운 오브젝트 타입을 만드는 내장 제네릭 Pick<T, K>을 구현하세요. 
    
    // 예시
    interface Todo {
      title: string
      description: string
      completed: boolean
    }
    
    type TodoPreview = MyPick<Todo, 'title' | 'completed'>
    
    const todo: TodoPreview = {
        title: 'Clean room',
        completed: false,
    }

     

     위 예시에서 MyPick은 Todo에서 title또는 completed 프로퍼티만 선택해 새로운 오브젝트 타입을 반환하는 내장 제네릭이다. 이를 구현하여야 한다. 

     

    /* _____________ 여기에 코드 입력 _____________ */
    
    type MyPick<T, K> = any
    
    /* _____________ 테스트 케이스 _____________ */
    import type { Equal, Expect } from '@type-challenges/utils'
    
    type cases = [
      Expect<Equal<Expected1, MyPick<Todo, 'title'>>>,
      Expect<Equal<Expected2, MyPick<Todo, 'title' | 'completed'>>>,
      // @ts-expect-error
      MyPick<Todo, 'title' | 'completed' | 'invalid'>,
    ]
    
    interface Todo {
      title: string
      description: string
      completed: boolean
    }
    
    interface Expected1 {
      title: string
    }
    
    interface Expected2 {
      title: string
      completed: boolean
    }

     

     테스트 케이스는 MyPick을 사용해서 Todo의 title만 선택한 오브젝트와 title 속성이 정의된 Expected1 인터페이스가 동일하다는 것, MyPick으로 Todo의 title 또는 complteted만 선택한 오브젝트와 title 속성과 completed 속성이 정의된 Expected2 인터페이스가 동일하다는 것이다. 

     

    type Equal<X, Y> = (<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y ? 1 : 2 ? true : false;

     

     Equal이 타입 X와 Y가 동일한지 확인하는 것은 이해했지만 문법들이 정확히 이해가 잘 안 갔다. TypeScript의 분배 법칙(distributive law)과 조건부 타입(Conditional Types)을 통해 표현한 것인데, 

     

    T extends X ? 1 : 2

     

    위부터 이해해 보자면 가변 타입 T가 X를 상속하거나 확장하는 개념이면 1을 반환, 그렇지 않으면 2를 반환시킨다. 

    (<T>() => T extends X ? 1 : 2)

     

    그리고 이를 타입 1또는 2를 반환하는 함수로 정의한다. 

     

    <T>() => T extends Y ? 1 : 2

     

    그러므로 위의 내용도 가변 타입 T가 Y를 상속하거나 확장하는 개념이면 1, 아니면 2를 반환시킨다는 뜻이다.

     

     

     종합해서 이해해보자면 가변 타입 T가 X를 상속했는지의 여부를 반환하는 함수가변 타입 T가 Y를 상속했는지의 여부를 반환하는 함수를 extends로 비교하여 특정 타입이 다른 타입을 확장하는지 여부를 판단한다. 이는 결국 두 함수 타입이 동일한지 확인하는 것이다. 동일하다면 true, 아니면 false를 리턴한다.

     

     

    다시 돌아와서, 아래를 구현해야 한다. 

    type MyPick<T, K> = any

     

    구현해야 될 내용은 T안에 있는 속성 K에 속한 프로퍼티를 선택하여 새로운 오브젝트 타입을 만드는 것이다. 속성 K에 속한 프로퍼티는 인덱스 시그니처를 사용하여 [key in K] : type; 으로 표현할 수 있다. 여기서는 T의 해당 프로퍼티 값을 선택하여 새로운 오브젝트 타입을 만들어야 하므로 type에 들어갈 키워드는 T[key]가 된다. 

     

     

    type MyPick<T, K extends keyof T> = {
     [key in K] : T[key]
    }

     

     

    7 - Readonly

    // T의 모든 프로퍼티를 읽기 전용(재할당 불가)으로 바꾸는 
    // 내장 제네릭 Readonly<T>를 이를 사용하지 않고 구현하세요.
    
    // 예시 
    interface Todo {
      title: string
      description: string
    }
    
    const todo: MyReadonly<Todo> = {
      title: "Hey",
      description: "foobar"
    }
    
    todo.title = "Hello" // Error: cannot reassign a readonly property
    todo.description = "barFoo" // Error: cannot reassign a readonly property

     

    /* _____________ 여기에 코드 입력 _____________ */
    
    type MyReadonly<T> = any
    
    /* _____________ 테스트 케이스 _____________ */
    import type { Equal, Expect } from '@type-challenges/utils'
    
    type cases = [
      Expect<Equal<MyReadonly<Todo1>, Readonly<Todo1>>>,
    ]
    
    interface Todo1 {
      title: string
      description: string
      completed: boolean
      meta: {
        author: string
      }
    }

     

    type MyReadonly<T> = {
      readonly [key in keyof T] : T[key]
    }

     

     T안의 속성들을 readonly로 바꿔주면 된다. 

     

    11 - Tuple to Object

    // 배열(튜플)을 받아, 
    // 각 원소의 값을 key/value로 갖는 오브젝트 타입을 반환하는 타입을 구현하세요.
    
    // 예시
    const tuple = ['tesla', 'model 3', 'model X', 'model Y'] as const
    
    type result = TupleToObject<typeof tuple> 
    // expected { 'tesla': 'tesla', 'model 3': 'model 3', 'model X': 'model X', 'model Y': 'model Y'}
    /* _____________ 여기에 코드 입력 _____________ */
    
    type TupleToObject<T extends readonly any[]> = any
    
    /* _____________ 테스트 케이스 _____________ */
    import type { Equal, Expect } from '@type-challenges/utils'
    
    const tuple = ['tesla', 'model 3', 'model X', 'model Y'] as const
    const tupleNumber = [1, 2, 3, 4] as const
    const sym1 = Symbol(1)
    const sym2 = Symbol(2)
    const tupleSymbol = [sym1, sym2] as const
    const tupleMix = [1, '2', 3, '4', sym1] as const
    
    type cases = [
      Expect<Equal<TupleToObject<typeof tuple>, { 'tesla': 'tesla', 'model 3': 'model 3', 'model X': 'model X', 'model Y': 'model Y' }>>,
      Expect<Equal<TupleToObject<typeof tupleNumber>, { 1: 1, 2: 2, 3: 3, 4: 4 }>>,
      Expect<Equal<TupleToObject<typeof tupleSymbol>, { [sym1]: typeof sym1, [sym2]: typeof sym2 }>>,
      Expect<Equal<TupleToObject<typeof tupleMix>, { 1: 1, '2': '2', 3: 3, '4': '4', [sym1]: typeof sym1 }>>,
    ]
    
    // @ts-expect-error
    type error = TupleToObject<[[1, 2], {}]>

     

     튜플의 경우 명시적으로 지정된 형식으로 아이템 순서를 설정, 배열 타입을 보다 특수하게 사용 가능한 타입이다.

    위의 테스트 케이스에서는 string, number, symbol이 요소로 있으니 any[]보다는 (string|number|symbol)[]로 바꿔주고,

    해당 요소에 접근할 때는 Indexed Access Types로 프로퍼티 타입을 추출시킨다. 제네릭 타입 T의 요소를 가져오려면 T[number]를 사용한다. 

     

    type TupleToObject<T extends readonly (string|number|symbol)[]> = {
      [key in T[number]] : key
    }

     

     

    14 - First of Array

    // 배열(튜플) T를 받아 첫 원소의 타입을 반환하는 제네릭 First<T>를 구현하세요.
    
    // 예시
    
    type arr1 = ['a', 'b', 'c']
    type arr2 = [3, 2, 1]
    
    type head1 = First<arr1> // expected to be 'a'
    type head2 = First<arr2> // expected to be 3

     

    /* _____________ 여기에 코드 입력 _____________ */
    
    type First<T extends any[]> = any
    
    /* _____________ 테스트 케이스 _____________ */
    import type { Equal, Expect } from '@type-challenges/utils'
    
    type cases = [
      Expect<Equal<First<[3, 2, 1]>, 3>>,
      Expect<Equal<First<[() => 123, { a: string }]>, () => 123>>,
      Expect<Equal<First<[]>, never>>,
      Expect<Equal<First<[undefined]>, undefined>>,
    ]
    
    type errors = [
      // @ts-expect-error
      First<'notArray'>,
      // @ts-expect-error
      First<{ 0: 'arrayLike' }>,
    ]

     

    간단하게 아래처럼 작성했었는데, 

    type First<T extends any[]> = T[0];
    
    // Expect<Equal<First<[]>, never>> 통과 못함

     

    통과하려면, [] 일 때는 never로 반환해주어야 한다.

     

    type First<T extends any[]> = T extends [] ? never : T[0];

     

     

     

     

     이번주 후반부터 시작하여 많이 풀지 못했는데 다음 주부터는 하루에 2문제씩 풀어보려고 한다. 역시 개념만 공부할 때와 직접 문제 해결을 해보는 건 차이가 큰 것 같다. 벌써 어렵다... 🔥

     

     

    참고자료

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

    https://github.com/type-challenges/type-challenges/blob/main/questions/00013-warm-hello-world/README.ko.md

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

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

    https://github.com/type-challenges/type-challenges/blob/main/questions/00011-easy-tuple-to-object/README.ko.md

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

    댓글

🍀 Y0ungZ dev blog.
스크롤 버튼