逐步推演:
type Chainable = {
option(key: string, value: any): any;
get(): any;
}
R
, get()
返回 R
type Chainable<R = object> = {
option(key: string, value: any): any;
get(): R;
}
option()
链式调用返回 Chainable
type Chainable<R = object> = {
option(key: string, value: any): Chainable;
get(): R;
}
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;
}
type Chainable<R = object> = {
option<K extends string, V>(
key: Exclude<K, keyof R>,
value: V
): Chainable<R & Record<K, V>>;
get(): R;
}
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 로직을 작성할 필요는 없습니다. 단지 타입 수준입니다.
key
는 string
만 허용하고 value
는 무엇이든 될 수 있다고 가정합니다. 같은 key
는 두 번 전달되지 않습니다.
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는 어떤 타입이든 될 수 있습니다. 제약이 없기 때문에, 이 타입 매개변수는 함수가 호출될 때 어떤 타입으로도 설정될 수 있습니다.
제네릭 타입을 사용하는 이유는 제네릭을 사용하면 함수나 클래스가 다양한 타입에 대해 동작할 수 있기 때문입니다.
/* _____________ 여기에 코드 입력 _____________ */
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 로직을 작성할 필요는 없습니다. 단지 타입 수준입니다.
key
는 string
만 허용하고 value
는 무엇이든 될 수 있다고 가정합니다. 같은 key
는 두 번 전달되지 않습니다.
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는 어떤 타입이든 될 수 있습니다. 제약이 없기 때문에, 이 타입 매개변수는 함수가 호출될 때 어떤 타입으로도 설정될 수 있습니다.
제네릭 타입을 사용하는 이유는 제네릭을 사용하면 함수나 클래스가 다양한 타입에 대해 동작할 수 있기 때문입니다.
/* _____________ 여기에 코드 입력 _____________ */
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