awesome-typescript
awesome-typescript copied to clipboard
「重学TS 2.0 」TS 练习题第三十题
完善 Chainable
类型的定义,使得 TS 能成功推断出 result
变量的类型。调用 option
方法之后会不断扩展当前对象的类型,使得调用 get
方法后能获取正确的类型。
declare const config: Chainable
type Chainable = {
option(key: string, value: any): any
get(): any
}
const result = config
.option('age', 7)
.option('name', 'lolo')
.option('address', { value: 'XiaMen' })
.get()
type ResultType = typeof result
// 期望 ResultType 的类型是:
// {
// age: number
// name: string
// address: {
// value: string
// }
// }
请在下面评论你的答案。
declare const config: Chainable
type Simplify<T> = {
[P in keyof T]: T[P]
}
type Chainable<T = {}> = {
// S extends string can make S is Template Literal Types
option<V, S extends string>(key: S, value: V): Chainable<T & {
// use Key Remapping in Mapped Types generate { S: V } type https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-1.html#key-remapping-in-mapped-types
[P in keyof {
S: S,
} as `${S}`]: V
}>
get(): Simplify<T>
}
const result = config
.option('age', 7)
.option('name', 'lolo')
.option('address', { value: 'XiaMen' })
.get()
type ResultType = typeof result
个人觉得这道题主要是要发现, config 可以进行链式调用, 这样可以很容易的联想到 js 中的 return this
这种思路, 那么这里 option
的返回值就应该是一个新的 Chainable
, 把添加了新属性的类型当做下一个 Chainable
的 T
即可
关于如何动态命名 key, 可以看一下官方介绍
// 给基础类型 T 增加 { K, V } 键值对
type ReudceType<K extends string, V extends any, T = {}> = T & { [key in [K] as `${K}`]: V }
type Chainable<T = {}> = {
option<K extends string, V extends any>(key: K, value: V): Chainable<ReudceType<K, V, T>>
get(): { [K in keyof T]: T[K] } // 这里也可以直接返回 T, 不过这样并不直观, 所以手动遍历一下
}
// 测试用例
const result = config
.option('age', 7)
.option('name', 'lolo')
.option('address', { value: 'XiaMen' })
.get()
type ResultType = typeof result
type Chainable<R = {}> = {
option<K extends string | number | symbol, V> (key: K, value: V): Chainable<R & Record<K, V>>
get (): R
}
declare const config: Chainable
type ITypes = string | number | symbol;
type Chainable<T = {}> = {
option<K extends ITypes, V extends any>(key: K, value: V): Chainable<T & Record<K, V>>;
get(): T;
}
const result = config
.option('age', 7)
.option('name', 'lolo')
.option('address', { value: 'XiaMen' })
.get()
type ResultType = typeof result
const t: ResultType = {
age: 30,
name: 'hello',
address: {
value: 'Huizhou'
}
}
console.log(t);
// 期望 ResultType 的类型是:
// {
// age: number
// name: string
// address: {
// value: string
// }
// }
思路: 链式操作的思维
declare const config: Chainable<{}>
type Chainable<T extends {}> = {
option<K extends PropertyKey, V>(key: K, value: V): Chainable<T & {
[p in K]: V
}>
get(): {
[p in keyof T]: T[p]
}
}
const result = config
.option('age', 7)
.option('name', 'lolo')
.option('address', { value: 'XiaMen' })
.option(3, true)
.get()
type ResultType = typeof result
declare const config: Chainable;
type Chainable<T0 = {}> = {
option<T, U>(key: keyof T, value: U): Chainable<T0 & { [P in keyof T]: U }>;
get(): T0;
};
const result = config.option("age", 7).option("name", "lolo").option("address", { value: "XiaMen" }).get();
type ResultType = typeof result;
// 期望 ResultType 的类型是:
// {
// age: number
// name: string
// address: {
// value: string
// }
// }
declare const config: Chainable
type Chainable<T = {}> = {
option<K extends keyof any, V>(key: K, value: V): Chainable<T & {[P in K]: V}>;
get(): T;
}
const result = config
.option('age', 7)
.option('name', 'lolo')
.option('address', { value: 'XiaMen' })
.get()
type ResultType = typeof result
已经有一样的了🤣
declare const config: Chainable;
type Chainable<T = {}> = {
option<K extends string, V extends any>(key: K, value: V): Chainable<{ [P in K]: V } & T>;
get(): T;
};
const result = config.option("age", 7).option("name", "lolo").option("address", { value: "XiaMen" }).get();
type ResultType = typeof result;
type Chainable<T = {}> = {
option<K extends PropertyKey, V = unknown>(
key: K,
value: V
): Chainable<
{
[X in keyof T | K]: X extends K ? V : X extends keyof T ? T[X] : never;
}
>;
get(): T;
};