00012-medium-chainable-options

Back

逐步推演:

  1. 初始状态
type Chainable = {
  option(key: string, value: any): any;
  get(): any;
}
  1. 定义泛型返回类型 R, get() 返回 R
type Chainable<R = object> = {
  option(key: string, value: any): any;

  get(): R;
}
  1. option() 链式调用返回 Chainable
type Chainable<R = object> = {
  option(key: string, value: any): Chainable;

  get(): R;
}
  1. option<K, V>() 定义泛型 K, V;Chainable 泛型叠加类型声明
type Chainable<R = object> = {
  option<K extends string, V>(
    key: K, 
    value: V
  ): Chainable<R & Record<K, V>>;

  get(): R;
}
  1. Key 的类型不可重复
type Chainable<R = object> = {
  option<K extends string, V>(
    key: Exclude<K, keyof R>, 
    value: V
  ): Chainable<R & Record<K, V>>;

  get(): R;
}
  1. key类型不同时,允许覆盖 Omit<R, K>
type Chainable<R = object> = {
  option<K extends string, V>(
    key: Exclude<K, keyof R>, 
    value: V
  ): Chainable<Omit<R, K> & Record<K, V>>;

  get(): R;
}

Solution by djdidi #37146

// 여기 풀이를 입력하세요
type Chainable<Props = {}> = {
  option<K extends string, T>(key: K, value: T): Chainable<Omit<Props, K> & { [key in K]: T }>;
  get(): Props
}

Solution by seungdeok #36734

type Chainable<T = {}> = { option<K extends string, V>( key: K, value: V ): Chainable<T & { [P in K]: V }> get(): T }

Solution by ChemieAi #36564

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
}

Solution by gakki-san #36438

type Chainable<T = {}> = {
  option<K extends keyof any,R>(
    key: K extends keyof T 
    ? R extends T[K]
    ? never : K 
    : K, value: R
    ): Chainable<Omit<T,K> & Record<K,R>>
  get(): T
}

Solution by gakki-san #36431

type Chainable<T = {}> = { option<K extends string, V>( key: K, value: V ): Chainable<T & { [P in K]: V }>

get(): T }

Solution by asylbekduldiev #36404

type Chainable<CurrentConfig = {}> = {
  option<OptionKey extends string, OptionValue>(
    key: Exclude<OptionKey, keyof CurrentConfig>,
    value: OptionValue
  ): Chainable<Omit<CurrentConfig, OptionKey> & Record<OptionKey, OptionValue>>;
  get(): CurrentConfig;
};

Solution by AleksandrShcherbackov #36161

type Chainable<T extends Record<keyof any, unknown> = {}> = {
  option<K extends keyof any, V>(
    key: K,
    value: V
  ): K extends keyof T
    ? Chainable<Omit<T, K> & Record<K, V>>
    : Chainable<Record<K, V> & T>
  get: () => T
}

Solution by Shunii85 #36041

type Chainable<R = {}> = {
  option<K extends string, V = any>(
    key: K extends keyof R ? never: K,
    value: V
  ): Chainable<Omit<R, K> & Record<K, V>>
  get(): R
}

Solution by user-9902 #36007

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
}

explain:

declare const a: Chainable

const result1 = a
  .option('foo', 123)
  .option('bar', { value: 'Hello World' })
  .option('name', 'type-challenges')
  .get()

we can find, a.option() can continue calling option or get, so we can kown option should return Chainable type, So

First, wo can add

type Chainable = {
  option: (key:string, value: any) => Chainable;
  get: any
}

Second, we need a generic parameter T to store type before the option call, and anthoer generic parameter K to avoid keys conflict, so we can get the code

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: any
}

Thrid, we add get return type

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
}

Solution by vaclock #35764

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;
};

Solution by gangnamssal #35554

// 你的答案
// ============= Test Cases =============
import type { Alike, Expect } from "./test-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;
};

// ============= Your Code Here =============
type Chainable<T = {}> = {
  option<Key extends string, Value>(
    key: Key extends keyof T ? never : Key,
    value: Value
  ): Chainable<{
    [k in Key | keyof T]: k extends keyof T
      ? k extends Key
        ? Value
        : T[k]
      : Value;
  }>;
  get(): T;
};

Solution by run-nan #35476

type Chainable<T = {}> = {
  option: <K extends PropertyKey, V>(
    key: K extends keyof T ? never : K,
    value: V
  ) => Chainable<Omit<T, K> & Record<K, V>>;
  get: () => T;
};

Solution by RanungPark #35453

type Chainable<T = {}> = {
  option<K extends string|number, V extends unknown>(
      key: K extends keyof T
          ? V extends T[K]
              ? never
              : K
          : K,
      value: V
  ): Chainable<Omit<T, K> & { [P in K]: V }>;
  get(): T;
};

Solution by HrOkiG2 #35280

type Chainable<R = unknown> = {
  option<T extends string, V extends any>(key: T extends keyof R ? never : T, value: V): Chainable<Omit<R, T> & Record<T, V>>
  get(): R
}

Solution by eunsukimme #35121

type Chainable<T = {}> = {
  option<K extends string, V>(key: K, value: V): Chainable<Omit<T, K> & Record<K, V>>;
  get(): T;
};

Solution by 2njeong #35083

type Chainable<T = {}> = {
  option<K extends string, V>(key: string, value: any): Chainable<T & {[P in K]: V}>
  get(): T
}

Solution by raeyoung-kim #35013

// 你的答案
type Chainable<T = {}> = {
  option<K extends string, V>(key: K extends keyof T ? never : K, value: V): Chainable<{
    [P in K]: V
  } & {
    [P in keyof T as P extends K ? never : P]: T[P]
  }>
  get(): T
}

Solution by heyuelan #34689

declare const config: Chainable<Result>

interface Result {
  foo: number
  name: string
  bar: {
    value: string
  }
}

type Chainable<T> = {
  options: <Key extends keyof T>(key: Key, val: T[Key]) => Chainable<Omit<T, Key>>
  get: () => T
}

const result = config.options('foo', 12)
  .options('name', 'foo')
  .options('bar', { value: 'bar' })
  .get()

Solution by semet #34645

type Chainable<T extends Record<string, any> = {}> = {
  option<K extends string, V extends any>(key: K extends keyof T ? never : K, value: V ): K extends keyof T ? Chainable<{ [P in K]: V }> : Chainable<T & { [P in K]: V }>
  get(): T
}
// your answers

Solution by Rustamaha #34597

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
}

Solution by devshinthant #34564

type Chainable<T = {}> = {
  option<K extends PropertyKey, V>(
    key: K extends keyof T ? never : K, value: V
  ): Chainable<Omit<T, K> & {[P in K]: V}>
  get(): {
    [P in keyof T]: T[P]
  } 
}

Solution by ktim816 #34434

type Chainable<T extends Record<string, any> = {}> = {
  option: <K extends string, V>(key: K extends keyof T ? never : K, value: V) => K extends keyof T ? Chainable<Omit<T, K> & Record<K, V>> : Chainable<T & Record<K, V>>
  get: () => T
}

Solution by rookie-luochao #34391

I couldn't find my solution under the accepted ones and many solutions with many upvotes don't allow overriding key-value pairs as required by the third test case. Many other solutions seem overly complicated.

type Chainable<Current = {}> = {
  option<K extends string, V>(key: K, value: V): Chainable<Omit<Current, K> & { [ Key in K as `${Key}` ]: V }>
  get(): Current
}

Type mapping allows for the creation of object types by mapping over union types. K can be thought of as a union with only one member.

The as keyword allows for the creation of a key name based on the value provided for key.

Before extending the current object Current, we must Omit from it any existing entry with the same key.

Solution by watisnu #34362

문제 설명

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

문제를 해결하기 위해 js/ts 로직을 작성할 필요는 없습니다. 단지 타입 수준입니다.

keystring만 허용하고 value는 무엇이든 될 수 있다고 가정합니다. 같은 key는 두 번 전달되지 않습니다.

풀이

options 메서드를 추가하여야

type Chainable<T = object> = {
	option: <K extends string, V>(
		key: K extends keyof T ? never : K,
		value: V
	) => Chainable<Omit<T, K> & Record<K, V>>;
	get: () => T;
};

<K extends string, V> 와 같은 부분은 ts에서 제네릭을 선언하는 부분입니다. 이 구문은 함수의 제네릭 타입 매개변수를 추가하여, 해당 함수가 다양한 타입에 대해 재사용 가능하도록 합니다. V는 어떤 타입이든 될 수 있습니다. 제약이 없기 때문에, 이 타입 매개변수는 함수가 호출될 때 어떤 타입으로도 설정될 수 있습니다. 제네릭 타입을 사용하는 이유는 제네릭을 사용하면 함수나 클래스가 다양한 타입에 대해 동작할 수 있기 때문입니다.

solution

/* _____________ 여기에 코드 입력 _____________ */

type Chainable<T = object> = {
	option: <K extends string, V>(
		key: K extends keyof T ? never : K,
		value: V
	) => Chainable<Omit<T, K> & Record<K, V>>;
	get: () => T;
};

/* _____________ 테스트 케이스 _____________ */
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;
};

Solution by adultlee #34139

문제 설명

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

문제를 해결하기 위해 js/ts 로직을 작성할 필요는 없습니다. 단지 타입 수준입니다.

keystring만 허용하고 value는 무엇이든 될 수 있다고 가정합니다. 같은 key는 두 번 전달되지 않습니다.

풀이

options 메서드를 추가하여야

type Chainable<T = object> = {
	option: <K extends string, V>(
		key: K extends keyof T ? never : K,
		value: V
	) => Chainable<Omit<T, K> & Record<K, V>>;
	get: () => T;
};

<K extends string, V> 와 같은 부분은 ts에서 제네릭을 선언하는 부분입니다. 이 구문은 함수의 제네릭 타입 매개변수를 추가하여, 해당 함수가 다양한 타입에 대해 재사용 가능하도록 합니다. V는 어떤 타입이든 될 수 있습니다. 제약이 없기 때문에, 이 타입 매개변수는 함수가 호출될 때 어떤 타입으로도 설정될 수 있습니다. 제네릭 타입을 사용하는 이유는 제네릭을 사용하면 함수나 클래스가 다양한 타입에 대해 동작할 수 있기 때문입니다.

solution

/* _____________ 여기에 코드 입력 _____________ */

type Chainable<T = object> = {
	option: <K extends string, V>(
		key: K extends keyof T ? never : K,
		value: V
	) => Chainable<Omit<T, K> & Record<K, V>>;
	get: () => T;
};

/* _____________ 테스트 케이스 _____________ */
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;
};

Solution by adultlee #34138

type Chainable = {
  option<K extends string, T>(key: K, value: T): SaveType<{
    [k in K]: T;
  }>;
}
type SaveType<Keys extends Record<string, any>> = {
  option<K extends string, T>(key: Exclude<K | keyof Keys, keyof Keys>, value: T): SaveType<{ [k in K]: T } & {
    [t in keyof Keys as t extends K ? never : t]: Keys[t];
  }>
  get(): {
    [t in keyof Keys]: Keys[t];
  }
}

Solution by LeonCry #34055

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
}

Solution by ouzexi #33988

type Chainable<R = object> = {
  option<K extends string, V>(
    key: Exclude<K, keyof R>, 
    value: V
  ): Chainable<Omit<R, K> & Record<K, V>>
  get(): R
}

Solution by notsecret32 #33890

type Chainable<T = {}> = {
  option: <K extends string, V>
  (key: K, value: V) => Chainable<Omit<T, K> & { [P in K]: V }>
  get: () => T
}

Solution by laplace1009 #33717