-
Notifications
You must be signed in to change notification settings - Fork 2
Open
Labels
Description
题目
题目链接:ChainableOption,回答链接
实现 Chainable
,满足下面要求
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();
type cases = [
Expect<Alike<typeof result1, Expected1>>,
Expect<Alike<typeof result2, Expected2>>
];
type Expected1 = {
foo: number;
bar: {
value: string;
};
name: string;
};
type Expected2 = {
name: string;
};
答案
方法一
type Chainable<T = unknown> = {
option<K extends string, V>(
key: K extends keyof T ? never : K,
value: V
): Chainable<T & { [k in K]: V }>;
get(): T;
};
知识点
用一个例子来讲解
declare const a: Chainable;
const result1 = a
.option("foo", 123)
.option("bar", { value: "Hello World" })
.get();
type Result1 = {
foo: number;
};
-
第一个
option
函数内部推导1. T = unknown, keyof T = never, K = "foo", V = 123 2. key = "foo" extends never => false => "foo", value = 123 3. T & {[k in K]: V} = unknown & {foo: 123} = {foo: 123} Chainable<{foo: 123}> T = {foo: 123}
-
第二个
option
函数内部推导// T 是第一个 option 函数的结果 1. T = {foo: 123}, keyof T = "foo", K = "bar", V = { value: "Hello World" } 2. key = "bar" extends "foo" => false => "bar", value = { value: "Hello World" } 3. T & {[k in K]: V} = {foo: 123} & {bar: { value: "Hello World" }} Chainable<{foo: 123} & {bar: { value: "Hello World" }}> T = {foo: 123} & {bar: { value: "Hello World" }}
-
调用
get
得到结果,返回T
如果调用 option
传入相同的 key
时,应该要报错,看下面的例子
const result2 = a
.option("name", "another name")
.option("name", "last name")
.get();
type Result2 = {
name: string;
};
-
第一个
option
推导出的结果是{name: "another name"}
-
第二个
option
的key
得到的值为never
K extends keyof T ? never : K => "name" extends "name" => true => K = never Chainable<{name: "another name"}> T = {name: "another name"}
-
调用
get
得到结果,返回T
方法二
type Chainable<T = unknown> = {
option<K extends string, V>(
key: K extends keyof T ? never : K,
value: V
): Chainable<{ [P in K | keyof T]: P extends keyof T ? T[P] : V }>;
get(): T;
};
知识点
用例子来讲解一步步的推算过程
declare const a: Chainable;
const result1 = a
.option("foo", 123)
.option("bar", { value: "Hello World" })
.get();
type Result1 = {
foo: number;
};
-
第一个
option
函数内部推导1. T = unknown, keyof T = never, K = "foo", V = 123 2. key = "foo" extends never => false => "foo", value = 123 3. 对象左边 = K | keyof T => "foo" | never => P = "foo" 对象右边 = "foo" extends never => false => 123 Chainable<{foo: 123}> T = {foo: 123}
-
第二个
option
函数内部推导// T 是第一个 option 函数的结果 1. T = {foo: 123}, keyof T = "foo", K = "bar", V = { value: "Hello World" } 2. key = "bar" extends "foo" => false => "bar", value = { value: "Hello World" } 3. 对象左边 = K | keyof T => "bar" | "foo" => P = "bar" 或者 "foo" 对象右边 = 当 P = "bar" 时,"bar" extends "foo" =? false => { value: "Hello World" } 当 P = "foo" 时,"foo" extends "foo" =? true => 123 Chainable<{bar: { value: "Hello World" }; foo: 123}> T = {bar: { value: "Hello World" }; foo: 123}
-
调用
get
得到结果,返回T
如果调用 option
传入相同的 key
时,应该要报错,看下面的例子
const result2 = a
.option("name", "another name")
.option("name", "last name")
.get();
type Result2 = {
name: string;
};
-
第一个
option
推导出的结果是{name: "another name"}
-
第二个
option
的key
得到的值为never
K extends keyof T ? never : K => "name" extends "name" => true => K = never Chainable<{name: "another name"}> T = {name: "another name"}
-
调用
get
得到结果,返回T
方法三
type Chainable = {
option<T, K extends string, V>(
this: T,
key: K extends keyof T ? never : K,
value: V
): T & { [key in K]: V };
get<T>(this: T): Omit<T, "option" | "get">;
};
知识点
这里的 this
指代 Chainable
本身,由于它本身包含了 option
和 get
两个方法,所以要用 Omit
把这两个方法去除掉。
TIP
{ [k in K]: V }
可以用Record<K, V>
代替K extends keyof T ? never : K
可以用Exclude<K, keyof T>
代替