Chainable Options - medium - [Type Challenge]

강성훈·2023년 1월 26일
0

type-challenges

목록 보기
19/20
post-thumbnail

by Anthony Fu @antfu

문제

체인 가능 옵션은 일반적으로 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
  }
}

솔루션

이번 타입은 제네릭이 필요없어 보이지만 option을 통해 넣는 값을 저장할 타입 변수 하나를 생성할 것입니다. 단 기본값을 설정해줄 것입니다.

type Chainable<T extends Record<string, any> = {}> = {};

변수 T가 하나의 저장공간이 되는 것입니다 이제 option을 먼저 구현 시킵니다.

옵션은 2개의 파라미터를 받는 함수입니다.
key는 string, value는 any타입입니다. 그러나 option을 다음과 같이 구현해서는 안됩니다.

option(
    key: string,
    value: any
  ): Chainable<{ [t in keyof T]: T[t] } & { [v in string]: any }>;

이렇게 한다면 우리가 원한 값과 달리 다음과 같이 나타납니다.

const result: {
    [x: string]: any;
} & {
    [x: string]: any;
}

그렇기에 option 또한 2개의 타입변수를 가질 것 입니다. Key, Value 키는 string타입이고, value는 any 타입입니다.

option<Key extends string, Value extends any>

그리고 이제 이 타입을 이용하여 이전 코드와 결합한다면 다음과 같이 됩니다.

option<Key extends string, Value extends any>(
    key: Key,
    value: Value
  ): Chainable<{ [t in keyof T]: T[t] } & { [v in Key]: Value }>;

이렇게 option 함수의 타입이 완성되었습니다.
key와 value 값을 받아 이전 T와 새로운 값을 결합하여 새로운 Chainable을 제네릭을 반환하는 것 입니다.

type Chainable<T extends Record<string, any> = {}> = {
  option<Key extends string, Value extends any>(
    key: Key,
    value: Value
  ): Chainable<{ [t in keyof T]: T[t] } & { [v in Key]: Value }>;
};

이제 마지막으로 get 함수를 구현할 것 입니다.
저장공간의 역할을 하고 있는 T객체를 반환 시키므로써 완성됩니다.

declare const config: Chainable;

type Chainable<T extends Record<string, any> = {}> = {
  option<Key extends string, Value extends any>(
    key: Key,
    value: Value
  ): Chainable<{ [t in keyof T]: T[t] } & { [v in Key]: Value }>;
  get(): T;
};

const result = config
  .option("foo", 123)
  .option("name", "type-challenges")
  .option("bar", { value: "Hello World" })
  .get();

// result
const result: {
    foo: number;
    name: string;
} & {
    bar: {
        value: string;
    };
}
profile
고등학생 주니어 개발자

0개의 댓글