타입 챌린지 34857 - Defined Partial Record

소파의 벨로그·2025년 6월 9일

타입챌린지

목록 보기
109/131

문제 링크

문제

키에 유니온 타입이 들어간 Record를 사용하는 것은 유니온의 일부를 사용하는 객체를 허락하지 않는다.

Using a Record with union types as keys doesn't allow you to make an object with only some of them

const record: Record<'a' | 'b' | 'c', number> = { a: 42, b: 10 }
// error: Property 'c' is missing in type '{ a: number; b: number; }'
// but required in type 'Record<"a" | "b" | "c", number>'

키에 유니온 타입이 들어간 Record를 Partial로 사용하는 것은 유티온의 모든 멤버가 없어도 동작하지만, 모든 key와 value가 옵셔널이며, 이후에 그들을 undefined로 놔둘 수 있다.

Using a Partial Record with union types as keys allows you to make an object without all union members, but makes all keys and values optional, potentially leaving them undefined

const partial: Partial<Record<'a' | 'b' | 'c', number>> = { a: 42 }
const partialType = typeof partial // { a?: number | undefined, b? : number | undefined, c? : number | undefined }
const operation = 0 + partial.a // error: 'partial.a' is possibly 'undefined'
const access = partial.c // possible, type doesn't know that there is no such key

당신은 두 세계의 장점을 모두 취하고 유니온의 모든 유형의 모든 조합을 생성하는 유형을 만들어야하므로,
객체에 존재하는 키를 사용하면 정의 된 유형이 제공되지만 객체가 아닌 유니온에 존재하는 키를 사용하면 오류가 발생한다.

You need to make a type that takes the best of both worlds, creates all combinations of all the types in the union, so using a key that exists in the object gives you a defined type, but using a key that exists in the union and not in the object throws an error

const best: DefinedPartial<Record<'a' | 'b' | 'c', number>> = { a: 42 }
const sum = 0 + best.a // 42
const error = best.b // error: property 'b' does not exist on type '{ a: number; }'

내 풀이

문제가 길지만, 그냥 모든 키의 조합을 나타내게 하는 mapped type을 만든 문제이다

type UnionMergeInTC<A,B,AU=A,BU=B>=
  A extends AU?
  B extends BU?
  Omit<A&B,never>
  :never
  :never


type DefinedPartialImpelment<T,U=keyof T>=
  U extends keyof T?
    {[R in U]:T[R]}|UnionMergeInTC<{[R in U]:T[R]},DefinedPartialImpelment<Omit<T,U>>>
  :never
  
type DefinedPartial<T> = DefinedPartialImpelment<T>

우선 사용처에서 구현용 제네릭을 사용하지 못하게 하기 위해 구현과 실제 타입을 분리했다.

그리고 객체 유니온 타입을 합치는 타입인 UnionMergeInTC를 만들었다.

이 타입을 사용하게 되면 UnionMergeInTC<{a:1},{b:2}|{c:3}>의 값은
{a:1,b:2}|{a:1,c:3}이 된다.

DefinedPartialImpelment에서는 T의 key중 하나를 골라
그 키만 값으로 갖는 객체 하나와,
그 객체와 선택된 키를 제외한 나머지 키를 사용한 값을 합치는 방식을 통해 구현했다.

다른 사람의 풀이

type DefinedPartial<T, K extends keyof T = keyof T> = K extends unknown
  ? T | DefinedPartial<Omit<T, K>>
  : never;

풀이

나와는 반대의 풀이 방식으로 코드를 짧게 만든 풀이이다

T에서 키 하나하나씩을 없애가며 객체를 유니온으로 만드는 접근 방식이다.

참고자료

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

0개의 댓글