FE/type-challenge
12. Chainable
최토피
2023. 9. 5. 12:14
728x90
문제
체인 가능 옵션은 일반적으로 Javascript에서 사용됩니다. 하지만 TypeScript로 전환하면 제대로 구현할 수 있나요?
이 챌린지에서는 option(key, value)
과 get()
두가지 함수를 제공하는 객체(또는 클래스) 타입을 구현해야 합니다. 현재 타입을 option
으로 지정된 키와 값으로 확장할 수 있고 get
으로 최종 결과를 가져올 수 있어야 합니다.
declare const config: Chainable
const result = config
.option('foo', 123)
.option('name', 'type-challenges')
.option('bar', { value: 'Hello World' })
.get()
// 결과는 다음과 같습니다:
interface Result {
foo: number
name: string
bar: {
value: string
}
}
cases
declare const a: Chainable
const result1 = a
.option('foo', 123)
.option('bar', { value: 'Hello World' })
.option('name', 'type-challenges')
.get()
const result2 = a
.option('name', 'another name')
// @ts-expect-error
.option('name', 'last name')
.get()
const result3 = a
.option('name', 'another name')
.option('name', 123)
.get()
type cases = [
Expect<Alike<typeof result1, Expected1>>,
Expect<Alike<typeof result2, Expected2>>,
Expect<Alike<typeof result3, Expected3>>,
]
type Expected1 = {
foo: number
bar: {
value: string
}
name: string
}
type Expected2 = {
name: string
}
type Expected3 = {
name: number
}
정답
type Chainable<T = {}> = {
option<U extends string, V>(
key: U extends keyof T
? V extends T[U]
? never
: U
: U,
value: V): Chainable<Omit<T, U> & Record<U, V>>
get(): T
}
풀이
조건
- option 함수로 두 인자 key, value의 타입을 추가할 수 있어야 한다.
- get 으로 지금까지의 옵션들이 포함된 object를 출력할 수 있어야 한다.
해설
우선, get으로 현재까지 object를 출력하기 위해서 제네릭을 추가한다.
type Chainable<T = {}> = {
option(key: string, value: any): any
get(): T
}
option은 chaining 할 수 있어야 하므로 Chainable을 출력하도록 한다.
type Chainable<T = {}> = {
option(key: string, value: any): Chainable<T>
get(): T
}
출력 시 key와 value가 반영되도록 수정한다. 이 때, key와 value의 타입이 반영되도록 제네릭을 추가한다.
type Chainable<T = {}> = {
option<U extends string, V>(key: U, value: V): Chainable<T & Record<U, V>>
get(): T
}
동일한 key가 들어왔을 때 오류가 발생하므로 이를 제거하는 코드를 추가한다.
type Chainable<T = {}> = {
option<U extends string, V>(key: U, value: V): Chainable<Omit<T, U> & Record<U, V>>
get(): T
}
정상적으로 동작하지만, 2번째 케이스에서 알 수 있듯 입력시 동일한 key, 동일한 타입의 value가 들어올 때 에러가 발생할 수 있어야하므로, U를 검사하는 로직을 추가한다.
type Chainable<T = {}> = {
option<U extends string, V>(
key: U extends keyof T
? V extends T[U]
? never
: U
: U,
value: V): Chainable<Omit<T, U> & Record<U, V>>
get(): T
}
혹시 오류나 개선점이 있다면 댓글 부탁드립니다.
728x90