체인 가능 옵션은 일반적으로 Javascript에서 사용됩니다. 하지만 TypeScript로 전환하면 제대로 구현할 수 있나요?
이 챌린지에서는 option(key, value)과 get() 두가지 함수를 제공하는 객체(또는 클래스) 타입을 구현해야 합니다. 현재 타입을 option으로 지정된 키와 값으로 확장할 수 있고 get으로 최종 결과를 가져올 수 있어야 합니다.
문제를 해결하기 위해 js/ts 로직을 작성할 필요는 없습니다. 단지 타입 수준입니다.
key는 string만 허용하고 value는 무엇이든 될 수 있다고 가정합니다. 같은 key는 두 번 전달되지 않습니다.
type Chainable<T extends object = {},> = {
option<KeyType extends Exclude<string,keyof T>,ValueType extends unknown>(key: KeyType, value: ValueType): Chainable<
{[R in keyof T| KeyType]:R extends KeyType? ValueType: R extends keyof T?T[R]:never}
>
get(): T
}
많은 시간을 고민했지만, 다음 두 코드에서 에러를 일으키지는 못했다
const result2 = a
.option('name', 'another name')
// @ts-expect-error
.option('name', 'last name')
.get()
const result3 = a
.option('name', 'another name')
// @ts-expect-error
.option('name', 123)
.get()
다음과 같은 과정을 거쳤다
type Chainable<T = {}> = {
option: <K extends string, V>(key: K extends keyof T ? never : K, value: V)
=> Chainable<T & Record<K, V>>
get: () => T
}
option에도 제네릭을 주는데, 그 제네릭이 keyof T에 대입 가능하면 never 타입을 key인자에 할당해 에러를 일으키는 방식을 사용하였다.
다만 이때 다음과 같이 중복되는 키가 들어오면 대응(뒤에 온 값을 덮어씀)을 하지 못하는 것을 깨닫고 다음과 같은 코드를 사용했다.
type Chainable<T = {}> = {
option: <K extends string, V>(key: K extends keyof T ?
V extends T[K] ? never : K
: K, value: V) => Chainable<T & Record<K, V>>
get: () => T
}
그 다음 같은 키가 중복되는 것을 방지하기 위해서 아래와 같은 코드로 변경했다.(Chainable의 첫 번째 인자에 Omit을 추가)
type Chainable<T = {}> = {
option: <K extends string, V>(key: K extends keyof T ?
V extends T[K] ? never : K
: K, value: V) => Chainable<Omit<T, K> & Record<K, V>>
get: () => T
}
그러나 마지막 개선 코드 역시 같은 키 값의 다른 타입의 value 타입이 들어오면 대응하지 못하기에 아래와 같이 개선할 수 있었다.
type Chainable<T = {}> = {
option: <K extends string, V>(key: K extends keyof T ?
never
: K, value: V) => Chainable<Omit<T, K> & Record<K, V>>
get: () => T
}
어짜피 K가 기존의 값과 겹치게 된다면 기존 K값을 Omit하기 때문에
그냥 K extends keyof T가 인정된다면 never를 첫 번째 인자로 주어 에러를 일으켰다.
https://github.com/type-challenges/type-challenges/issues/13951