미라클 글쓰기 2일차
Type Challenge 는 Anthony Fu 라는 개발자가 만든 Typescript 문제은행이다.
보통은 Typescript 기능 중 자주 쓰던 기능만 사용하게 되는데, 그러면 TS 만의 강력한 Type System을 배울 일이 없어진다.
TS 의 강력한 Type System을 이용하여 주어진 문제를 해결해 나가다 보면 자연스럽게 TS의 Type System을 익히게 된다.

처음 Type Challenge 의 문제를 풀었을 때는 상당히 많은 일을 compile time에 처리할 수 있다는 사실에 놀랐다.
꽤 오랫동안 TS를 사용해 왔는데 이렇게까지 TS의 type에 대해 몰랐다는 사실에 반성하며 Type Challenge 문제풀이를 올려볼까 한다.
정답은 워낙 많은 사이트에 공유가 되어 있으니, 나는 해설이나 다른 언어와의 차이점 등을 써볼까 한다.

Type Challenge

Type Challenge를 시작하려면 우선 다음 github url을 clone하자.
이후 pnpm install & generate 하면 로컬 환경에 문제가 배포된다.

$ git clone https://github.com/type-challenges/type-challenges.git

# pnpm install & generate 
$ pnpm install

#  문제가 업데이트 되더라도 나의 풀이를 유지하고 싶으면 -K 옵션을 설정, 문제를 generate 한다.
$ pnpm generate -K

문제는 Easy / Medium / Hard / Extreme 의 4 단계로 나누어져 있는데, Easy 나 Medium 은 그닥 난이도 차이가 나는 것 같지는 않다.
Hard / Extreme 은 아직 안풀어봤다.

easy – Pick

문제 링크: https://github.com/type-challenges/type-challenges/blob/main/questions/00004-easy-pick/README.md

  4 - Pick
  -------
  by Anthony Fu (@antfu) #쉬움 #union #built-in

  ### 질문

  `T`에서 `K` 프로퍼티만 선택해 새로운 오브젝트 타입을 만드는
  내장 제네릭 `Pick<T, K>`을 이를 사용하지 않고 구현하세요.

  예시:

  ```ts
  interface Todo {
    title: string
    description: string
    completed: boolean
  }

  type TodoPreview = MyPick<Todo, 'title' | 'completed'>

  const todo: TodoPreview = {
      title: 'Clean room',
      completed: false,
  }
  ```

  > GitHub에서 보기: https://tsch.js.org/4/ko

Typescript 는 자주 쓰이는 타입 변환에 대한 built-in utility type을 제공한다.(링크)
이러한 built-in type 중 Pick<T, K> 를 직접 구현하는 문제다.

이 문제를 풀기 위해 사전에 알아야 할 지식은 다음과 같다.

  • Union type
  • keyof Operator
  • in Operator와 Mapped type
  • Indexed access type

Union Type

Union Type은 서로 다른 2개 이상의 타입을 조합하여 하나의 타입처럼 사용하는 것을 말한다.

type MyPropertyKey = string | number | symbol;

근데 Union Type을 잘 보면 type 의 array 처럼 생겼다. 실제로 Typescript 의 compiler 를 살펴보면 Union Type은 array로 관리되고 있음을 알 수 있다.
Union type 이 Array로 관리되고 있다는 사실을 잘 기억해 두자.

    // Add the given types to the given type set. Order is preserved, duplicates are removed,
    // and nested types of the given kind are flattened into the set.
    function addTypesToUnion(typeSet: Type[], includes: TypeFlags, types: readonly Type[]): TypeFlags {
        let lastType: Type | undefined;
        for (const type of types) {
               ...

keyof Operator

keyof Operator는 type operator로, object type의 key로 구성된 Union type을 생성한다.

type Point = { x: number; y: number };
type P = keyof Point; // type P = "x" | "y";

Mapped Type

Mapped Type의 Map은 Array.map(x => x * 2) 의 그 Map function 이다.
in Operator 는 Union type을 구성하는 type의 Array를 Map function 처럼 변환하는 type Operator이다.(위에서 언급한 것 처럼 Union type은 Array처럼 취급한다.)
type 변환을 반복적으로 수행해야 할 때 아래와 같이 편리하게 쓸 수 있다.

type Foo = 'A' | 'B' | 'C';
type MappedFoo = { [k in Foo]: string }; 

const mFoo: MappedFoo = {
  A: 'hello',
  B: 'world'
}

Indexed Access Type

Indexed Access Type은 과거에는 lookup type으로 불렸던, 어떤 type의 property 에 index를 지정하여 접근, type을 조합하는 방식이다.

type Person = { age: number; name: string; };
type Age = Person["age"]; // type Age = number

문제 풀이

배운 것들을 종합하여 문제를 풀어보자.
문제는 1) Generic Type T 에서 2) Generic Type K property만을 선택하는 type MyPick을 조합하는 것이다.

문제의 조건에 따라 T는 반드시 object type이다.
문제의 조건에 따라 K 는 반드시 T의 sub-property 집합이다. 따라서 K는 union type 이고, K extends keyof T 가 성립한다.
object type T에서 K property 만을 포함하는 새로운 object type을 추려야 하므로 My Pick 은 다음과 같이 표현할 수 있다.

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

댓글 남기기

인기 검색어

01010011에서 더 알아보기

지금 구독하여 계속 읽고 전체 아카이브에 액세스하세요.

계속 읽기