可串联构造器
题目
在 JavaScript 中我们很常会使用可串联(Chainable/Pipeline)的函数构造一个对象,但在 TypeScript 中,你能合理的给他附上类型吗?
在这个挑战中,你可以使用任意你喜欢的方式实现这个类型 - Interface, Type 或 Class 都行。
你需要提供两个函数 option(key, value)
和 get()
。
在 option
中你需要使用提供的 key 和 value 扩展当前的对象类型,通过 get
获取最终结果。
例如
declare const config: Chainable
const result = config
.option('foo', 123)
.option('name', 'type-challenges')
.option('bar', { value: 'Hello World' })
.get()
// 期望 result 的类型是:
interface Result {
foo: number
name: string
bar: {
value: string
}
}
你只需要在类型层面实现这个功能 - 不需要实现任何 TS/JS 的实际逻辑。
你可以假设 key
只接受字符串而 value
接受任何类型,你只需要暴露它传递的类型而不需要进行任何处理。同样的 key
只会被使用一次。
题目来源:https://tsch.js.org/12/zh-CN
解答
type Chainable<T = {}> = {
option<Key extends string, Value>(key: Key extends keyof T ? never : Key, value: Value): Chainable<T & Record<Key, Value>>;
get(): T
}
第一步:使用泛型约束,限制key必须是string类型
type Chainable<T = {}> = {
option<Key extends string, Value>(key: string, value: any): any
get(): any
}
第二步:对option的参数类型进行约束
type Chainable<T = {}> = {
option<Key extends string, Value>(key: Key, value: Value): any
get(): any
}
第三步:使用递归,处理串联调用的类型返回
type Chainable<T = {}> = {
option<Key extends string, Value>(key: Key, value: Value): Chainable<T & Record<Key, Value>>
get(): any
}
第四步:处理get方法的返回类型
type Chainable<T = {}> = {
option<Key extends string, Value>(key: Key, value: Value): Chainable<T & Record<Key, Value>>
get(): T
}
第五步:处理相同类型key注入的报错,添加判断
type Chainable<T = {}> = {
option<Key extends string, Value>(key: Key extends T ? never : Key, value: Value): Chainable<T & Record<Key, Value>>
get(): T
}