Skip to main content

可串联构造器

题目

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