[TS] type challenge

장동균·2022년 9월 22일
0

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

해당 링크의 타입 챌린지 문제들을 하루에 2문제씩 풀어보면서 기록을 남기기 위함.


easy - 4 pick

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

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

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

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

타입스크립트의 Pick<Types, keys> 이라는 유틸리티 타입을 직접 구현해보는 문제. 어려운데 easy라니...


easy - 7 readonly

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> = { readonly [key in keyof T]: T[key] }

이건 조금 해볼만 했던 문제


easy - 11 Tuple to Object

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 (string|number)[]> = {
	[key in T[number]]: key
}

배열의 특징을 안다면 indextypenumber이고 이를 활용해야 하는 문제


easy - 14 First of Array

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

// 정답 1
type First<T extends any[]> = T extends [] ? never : T[0]

// 정답 2
type First<T extends any[]> = T['length'] extends 0 ? never : T[0]

ts에서는 이런 식으로 조건문을 사용...
빈배열의 경우 never를 반환해야하는데 이에 대한 처리가 조금 까다로웠던 문제


easy - 18 length of tuple

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 extends readonly string[]> = T['length']

이전 문제랑 비슷해서 쉽게 해결할 수 있었던 문제


easy - 43 exclude

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

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

never를 거의 안 써봐서 어려웠다... never를 정리한 토스트 문서


easy - 189 awaited

type ExampleType = Promise<string>

type Result = MyAwaited<ExampleType> // string

// 정답
type MyAwaited<T> = T extends Promise<infer P> ? MyAwaited<P> : T

이런 재귀적인 느낌의 코드, infer 모두 익숙하지 않아... 꽤 많이 어려웠던 문제

해당문서를 통해 조금 더 infer 키워드에 대해 공부할 수 있었다.


easy - 268 if

type A = If<true, 'a', 'b'>  // expected to be 'a'
type B = If<false, 'a', 'b'> // expected to be 'b'

// 정답
type If<C extends boolean, T, F> = C extends true ? T : F

easy - 533 concat

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

// 정답
type Concat<T extends unknown[], U extends unknown[]> = [...T, ...U]

easy - 3057 push

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

// 정답
type Push<T extends unknown[], U> = [...T, U]

easy - 3060 unshift

type Result = Unshift<[1, 2], 0> // [0, 1, 2,]

// 정답
type Unshift<T extends unknown[], U> = [U, ...T]

easy - 3312 parameters

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

type FunctionParamsType = MyParameters<typeof foo> // [arg1: string, arg2: number]
                                       
// 정답
type MyParameters<T extends (...args: any[]) => any> = T extends (...args: infer R) => any ? R : never;                                 

typeof 함수T extends (...args: any[]) => any의 형태를 가진다는 사실을 기억하자!


normal - 2 get return type

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

type a = MyReturnType<typeof fn> // should be "1 | 2"
                      
// 정답
type MyReturnType<T extends Function> = T extends (...args: any[]) => infer U ? U : never;

함수의 반환값에 대한 타입을 제네릭 U로 기억하고 이를 반환시키면 되는 문제. 함수의 경우 반환값이 없는 경우도 존재하기 때문에 이 경우 타입은 never로 대체

infer 자체가 조건부 타입에서만 사용되고 boolean의 느낌이라고 받아들이면 이해하기 좋을 것 같다.


normal - 3 omit

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

type TodoPreview = MyOmit<Todo, 'description' | 'title'>

const todo: TodoPreview = {
  completed: false,
}

// 정답
type MyExclude<T, U> = T extends U ? never : T

type MyOmit<T, K extends keyof T> = { [U in MyExclude<keyof T, K>]: T[U] }

normal - 8 Readonly 2

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

const todo: MyReadonly2<Todo, 'title' | 'description'> = {
  title: "Hey",
  description: "foobar",
  completed: false,
}

todo.title = "Hello" // Error: cannot reassign a readonly property
todo.description = "barFoo" // Error: cannot reassign a readonly property
todo.completed = true // OK

// 정답
type MyReadonly2<T, K extends keyof T = keyof T> = {
  readonly [Key in  K]: T[Key]
} & {
  [Key in Exclude<keyof T, K>]: T[Key]
}

= keyof T 가 어떠한 의미인지 확인 필요


normal - 9 Deep ReadOnly

type X = { 
  x: { 
    a: 1
    b: 'hi'
  }
  y: 'hey'
}

type Expected = { 
  readonly x: { 
    readonly a: 1
    readonly b: 'hi'
  }
  readonly y: 'hey' 
}

type Todo = DeepReadonly<X>

// 정답
type DeepReadonly<T> = {
  readonly [K in keyof T]: keyof T[K] extends never ? T[K] : DeepReadonly<T[K]>;
};

재귀 느낌의 문제. 이 문제에서의 핵심은 원시타입은 그대로 readonly를 적용하고 객체는 readonly를 적용하되, 내부에도 다시 readonly를 적용해야하는 점에 있었다.

keyof T extends never 구문이 가장 중요했는데 이곳에 의하면 known key를 확인하는 로직이라고 한다. known key라는 개념이 확 와닫지는 않은데 잘 외워두면 써먹을 곳이 많은 구문인 것 같다.


normal - Tuple to Union

type Arr = ['1', '2', '3']

type Test = TupleToUnion<Arr> // expected to be '1' | '2' | '3'

// 정답
type TupleToUnion<T extends unknown[]> = T[number]; 

이전에 풀어봤던 문제와 거의 동일한 문제


normal - Last of Array

type arr1 = ['a', 'b', 'c']
type arr2 = [3, 2, 1]

type tail1 = Last<arr1> // expected to be 'c'
type tail2 = Last<arr2> // expected to be 1

// 정답
type Last<T extends any[]> = T extends [...infer rest, infer last] ? last : never;

normal - Pop

type arr1 = ['a', 'b', 'c', 'd']
type arr2 = [3, 2, 1]

type re1 = Pop<arr1> // expected to be ['a', 'b', 'c']
type re2 = Pop<arr2> // expected to be [3, 2]

// 정답
type Pop<T extends any[]> = T extends [...infer rest, infer last] ? rest : [];

StartsWith

type a = StartsWith<'abc', 'ac'> // expected to be false
type b = StartsWith<'abc', 'ab'> // expected to be true
type c = StartsWith<'abc', 'abcd'> // expected to be false

// 정답
type StartsWith<T extends string, U extends string> = T extends `${U}${infer L}` ? true : false;
type StartsWith<T extends string, U extends string> = T extends `${U}${string}` ? true : false;

EndsWith

type a = EndsWith<'abc', 'bc'> // expected to be true
type b = EndsWith<'abc', 'abc'> // expected to be true
type c = EndsWith<'abc', 'd'> // expected to be false

// 정답
type EndsWith<T extends string, U extends string> = T extends `${infer L}${U}` ? true : false;
type EndsWith<T extends string, U extends string> = T extends `${string}${U}` ? true : false;

infer keyword 문서 꽤나 많이 어렵,,.

profile
프론트 개발자가 되고 싶어요

1개의 댓글

comment-user-thumbnail
2022년 10월 15일

카톡외않돼나요

답글 달기