easy 와 비교하였을 때 medium의 난이도가 크게 높지는 않다. 다만 깨대로운 edge case 들을 처리하는 꼼꼼함이 요구된다.
문제의 수가 워낙 많아 절반 가량 풀었는데도 아직 갈 길이 멀다.
medium – Get Return Type
Get Return Type 은 medium에 걸맞지 않게 난이도가 낮은 편이지만 매우 유용한 내장 제네릭 ReturnType을 구현하는 문제기 때문에 다뤄봤다.
내장 제네릭 ReturnType<T>을 이를 사용하지 않고 구현하세요.
예시:
const fn = (v: boolean) => {
if (v)
return 1
else
return 2
}
type a = MyReturnType<typeof fn> // should be "1 | 2"
주어진 Generic Type T 가 함수형일 경우, 함수의 Return Type을 알아내면 되는 문제이다.
지난 시간에 배웠던 extends 를 사용하여 T가 함수형 타입일 경우를 분기 처리하고,infer keyword를 사용하여 함수형 타입 T 의 Return Type을 표현해 보자.
type MyReturnType<T> = T extends (...a) => infer R ? R : T;
medium – Omit
3 - Omit
-------
by Anthony Fu (@antfu) #보통 #union #built-in
### 질문
`T`에서 `K` 프로퍼티만 제거해 새로운 오브젝트 타입을 만드는 내장 제네릭 `Omit<T, K>`를 이를 사용하지 않고 구현하세요.
예시:
```ts
interface Todo {
title: string
description: string
completed: boolean
}
type TodoPreview = MyOmit<Todo, 'description' | 'title'>
const todo: TodoPreview = {
completed: false,
}
```
> GitHub에서 보기: https://tsch.js.org/3/ko
마찬가지로 유용하게 쓸 수 있는 내장 Generic 인 Omit 을 구현하는 문제다.
Mapped Type의 key를 re-mapping 하는 문법인 as 를 사용하여 풀어야 한다.
Key re-mapping in Mapped Type(=as keyword)
흔히들 알고 있는 type assertion에서 사용되는 as keyword 와는 다른 문법으로,
Mapped Type의 key를 다른 형태로 변경하는 용도로 쓰인다.
예를 들면, string 과 number가 섞여있는 Union Type을 key로 갖는 Mapped Type을 만들었는데,
string type의 key는 제외하고 싶을 때 아래와 같이 쓴다.
type Card = 1 | 2 | 3 | ...| 'Jack' | 'Queen' | 'King';
type OnlyNumber = { [key in Card as number]: any };
const Foo: OnlyNumber = {1: 'Diamond' }; // ok
const Bar: OnlyNumber = {'King': 'Heart' }; // error
문제에서는 K type을 제외한 Mapped Type을 만드는 것이 목적이므로,
다음과 같이 Mapped Type의 key 가 K type과 같다면 제외하는 방식으로 as keyword를 사용하면 된다.
// as 로 key를 key extends K ? never : key 로 re-map 하였다.
type MyOmit<T, K extends keyof T> = { [key in keyof T as key extends K ? never : key]: T[key] };
medium – Chainable Options
12 - Chainable Options
-------
by Anthony Fu (@antfu) #보통 #application
### 질문
체인 가능 옵션은 일반적으로 Javascript에서 사용됩니다. 하지만 TypeScript로 전환하면 제대로 구현할 수 있나요?
이 챌린지에서는 `option(key, value)`과 `get()` 두가지 함수를 제공하는 객체(또는 클래스) 타입을 구현해야 합니다. 현재 타입을 `option`으로 지정된 키와 값으로 확장할 수 있고 `get`으로 최종 결과를 가져올 수 있어야 합니다.
예시
```ts
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
}
}
```
문제를 해결하기 위해 js/ts 로직을 작성할 필요는 없습니다. 단지 타입 수준입니다.
`key`는 `string`만 허용하고 `value`는 무엇이든 될 수 있다고 가정합니다. 같은 `key`는 두 번 전달되지 않습니다.
> GitHub에서 보기: https://tsch.js.org/12/ko
medium 난이도에 어울리는 까다롭고 흥미진진한 문제이다. Type System 을 활용하여 option() 메소드와 get() 메소드를 마치 Type Generation을 위한 Builder Pattern처럼 구현해야 한다.
특별히 새로 나오는 개념은 없으며, Type Recursion, Intersection Type 을 사용하여 구현하도록 한다.
먼저 option(key, value) 메소드를 구현해보자. 다음과 같이 key 와 value의 Type을 Generic Type으로 지정하자.option<K, V>(key: K, value: V)
일반적으로 Builder Pattern은 자기 자신의 reference 를 넘겨 chaining하면서 property들을 초기화하는데,
Chainable에서도 비슷한 방식으로 Type Recursion을 통해 지금껏 작성한 type 정보를 chaining할 수 있다.
그러기 위해 최초의 Chainable에 다음과 같이 빈 Object를 Type Parameter로 전달하도록 한다. 모든 chaining이 끝나면 이 빈 Object를 get() 메소드로 반환하면 원하는 바를 이룰 수 있을 것이다.type Chainable<T = {}>
이제 option() 이 호출될 때 마다 type 정보를 merge 하도록 & operator를 사용하여 Intersection Type을 만들어보자.option<K, V>(key: K, value: V): Chainable<T & { [k in K]: V }>;
지금까지 내용을 다 조합하면 다음과 같다.
type Chainable<T = {}> = {
option<K, V>(key: K, value: V): Chainable<T & {[k in K]: V}>
get(): T;
}
Chainable type의 핵심적인 구현은 끝났으니, 이제 문제의 제약조건들을 만족시켜 테스트 케이스를 통과하도록 해보자.
K는 string type 만 허용하므로, option<K extends string, V>
같은 key는 두 번 전달될 수 없으므로option<K extends string, V>(key: K extends keyos T ? never : K, value: V):Chainable<Omit<T, K> & {[k in K]: V}>
다 조합한 최종 답안은 다음과 같다.
type Chainable<T = {}> = {
option<K extends string, V>(key: K extends keyof T ? never : K, value: V):
Chainable<Omit<T, K> & {[key in K]: V}>
get(): T;
}





댓글 남기기