Yuzhe's Blog

yuzhes

可串联构造器

可串联构造器

题目链接

题目

在 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