본문 바로가기
Language/Typescript

타입챌린지 : 2757-PartialByKeys (medium)

by hsloth 2023. 5. 11.

 

이 글은 제가 타입챌린지를 하면서 해석한 내용을 적는 글입니다. 틀린 내용이 있으면 댓글 달아주시면 감사하겠습니다.

 

https://github.com/type-challenges/type-challenges/blob/main/questions/02757-medium-partialbykeys/README.md

 

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

 

제네릭 인자 T와 K를 받아서, K에 해당하는 속성(키 값)을 optional로 만들어주는 타입이다.

 

나는 풀다가 Intersection을 썼으면 좋을텐데... 라고 생각했다. 하지만, Intersection Type을 사용해버리면 객체가 아닌 Intersection Type으로 인식을 해버리니... 사용을 못할 거라고 생각했다.그렇지만 그것은 착각이었다!다른 분들의 풀이를 보니... Intersection을 Object로 변경해서 답을 풀었더라..... ㅠㅠ

type IntersectionToObject<T> = {
  [P in keyof T]: T[P]
}

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


/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'

interface User {
  name: string
  age: number
  address: string
}

interface UserPartialName {
  name?: string
  age: number
  address: string
}

interface UserPartialNameAndAge {
  name?: string
  age?: number
  address: string
}

type cases = [
  Expect<Equal<PartialByKeys<User, 'name'>, UserPartialName>>,
  Expect<Equal<PartialByKeys<User, 'name' | 'age'>, UserPartialNameAndAge>>,
  Expect<Equal<PartialByKeys<User>, Partial<User>>>,
  // @ts-expect-error
  Expect<Equal<PartialByKeys<User, 'name' | 'unknown'>, UserPartialName>>,
]

IntersectionToObject라는 타입을 만들어서 Intersection Type을 Object로 바꾸어줄 수 있도록 한다.

type PartialByKeys<T extends {}, K extends keyof T = keyof T> : PartialByKeys라는 타입을 정의한다. T는 객체만 받고, K는 T의 key들만 받되, 디폴트는 T의 키값이라고 정의해준다.

[P in keyof T as P extends K ? P : never]?: T[P] : T의 키 값을 P라고 하는데, P가 K라면 P를 리턴하고 아니라면 리턴하지 않는다. (?을 위해서 K를 상속 받는 값만 리턴해준다)

[P in keyof T as P extends K ? never : P]: T[P] : 이번엔 반대로 P가 K라면 never를 리턴한다. (Partial이 아닌 속성들을 리턴한다)

 

 

그리고 이렇게 푸는 분도 계셨다.

type PartialByKeys<T extends {}, U extends keyof T = keyof T> = 
  Omit<Partial<Pick<T, U & keyof T>> & Omit<T, U & keyof T>, never>;

Partial<Pick<T, U & keyof T>> : T에서 U와 keyof T에서 공통된 key들을 선택한다음 ?를 붙여준다. (A)

Omit<T, U & keyof T> : T에서 방금 ?를 붙여준 key들을 제거한다. (B)

그리고 마지막으로 Omit<A & B, never> 를 거치면 A와 B는 Intersection Type이 아닌 객체가 되어서 반환된다.

 

따라서, 위의 답도 이렇게 작성할 수 있다.

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