可串联构造器
大约 1 分钟TC-Medium
可串联构造器
题目
在 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
}
}
解答
这是一个非常有趣的话题。在诸多框架中,我们常能看到这样的结构来保证更好的类型安全,以至于端到端的类型安全(End-to-End Type Safety)。
其中一个典型的例子是 Elysia.js
type Chainable<T = {}> = {
option<K extends string, V>
// not allow duplicate keys
(key: K extends keyof T ? never : K, value: V):
// avoid duplicate keys in T
Chainable<Omit<T, K> & Record<K, V>>
get(): T
}
我们使用T
来存储当前的Result
类型。
特别的,当key
已经存在于T
中时,我们使用never
来阻止重复的key
。
注意,虽然这里会提示错误,但是 TypeScript 仍会尝试继续推导类型。于是我们要再次使用Omit
来排除重复的key
。