일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- tanstackquery
- go
- ReactHooks
- Chakra
- test
- 웹애플리케이션서버
- css
- JPA
- typescript
- java
- storybook
- component
- 티스토리챌린지
- react-hook-form
- javascript
- springboot
- Spring
- React
- 오블완
- RTK
- Gin
- backend
- designpatterns
- frontend
- golang
- Redux
- hook
- JavaSpring
- satisfiles
- Today
- Total
bkdragon's log
Chainable Options 본문
타입챌린지 Chainable Options 문제를 풀어보자. 단순한 문제 풀이로만 글을 쓰고 싶지 않은데, 이 문제는 답을 이해하는 것도 꽤나 어려웠다. 고민하면서 얻은 노하우와 같이 설명해보겠다. 아래는 문제의 링크이다.
우선 문제의 감을 잡는데는 테스트 케이스를 보는것이 꽤나 도움이 된다.
import type { Alike, Expect } from '@type-challenges/utils'
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')
// @ts-expect-error
.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
}
처음 눈에 들어오는 것은 Chainable 타입에 제너릭이 없다는 점이다. 기본값을 줘야할 수 있다는 힌트가 된다.
다음은 에러가 발생하는 부분이다. 같은 키값을 넣으면 에러가 발생한다. 그리고 나중에 입력된 값만이 남는 것도 확인 할 수 있다(Expected3).
문제와 별개로 이 두가지 정보를 테스트 케이스에서 얻을 수 있다.
처음 얻은 정보를 통해 제네릭에 기본값을 줘야될 수 있다는 것을 알았다. 문제에서 다루는 자료구조가 객체이기 때문에 다음과 같이 줄 수 있다.
type Chainable<T ={}> = {
option(key: string, value: any): any
get(): any
}
다음 정보는 기존에 객체에 포함되는 키 값을 줄 수 없다는 점이다. option 함수에 제네릭을 줘서 구현할 수 있다.
type Chainable<T ={}> = {
option<K extends string, V>(key: K extends keyof T ? never : K, value: V): any
get(): any
}
이제 option의 반환값에 대해 고민해보자. option을 체이닝 할수록 만들고 있는 객체가 점점 완성(정확히는 key와 value가 쌓여간다.)이 되어간다. 그리고 그 객체 역시 option을 가지고 있어야한다 (get하기 전까진). 처음 값에서 새로운 key와 value가 합쳐진 객체이면서 option을 가지는, 즉 Chainable에 key와 value가 추가된 타입을 제네릭으로 받는 형태를 반환해야한다.
아래는 기존의 T에 새로운 key value로 만든 객체를 합친 타입을 제네릭으로 갖는 Chinable을 반환한다.
type Chainable<T ={}> = {
option<K extends string, V>(key: K extends keyof T ? never : K, value: V): Chainable<T & Record<K, V>>
get(): any
}
get은 간단하다. 현재 T를 반환하면 된다.
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의 key와 같은 값은 뺴줘야한다.
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
}
모든 오류가 사라졌다.
타입 레벨에서 기존에 존재하는 key와 같은 값을 받지 못하게 했지만 실제로 그것은 별개의 문제가 되는 것 같다. 타입을 선언하는 부분과 테스트 케이스를 잘 살펴보며 문제를 풀어보자.
'Typescript' 카테고리의 다른 글
Template Literal Types (0) | 2023.04.04 |
---|---|
Conditional types (0) | 2023.03.22 |
Deep Readonly (0) | 2023.03.10 |
key in keyof T as key extends K ? never : key ??? (0) | 2023.03.03 |
T[number] 란? (0) | 2023.03.02 |