Front-End(Web)/Typescript

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

ttaeng_99 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의 유니온 타입이다. (참고링크)

 

반응형