重用动态方法的接口

时间:2018-02-02 15:44:40

标签: typescript typescript-typings

我有一个模块,它包装了库的所有方法,使它们可以链接,所以代替:

const result = lib.three(lib.two(lib.one('abc')))

用户可以这样做:

const result = new Chainable('abc').one().two().three().value

要做到这一点,我的代码,一个类,包装每个方法:

import lib from 'lib'

class Chainable {
  public value: any

  constructor (value: any) {
    this.value = value
  }
}

const methods = Object.keys(lib)

for (const method of methods) {
  Chainable.prototype[method] = function (this: Chainable) {
    this.value = Reflect.apply(lib[method], null, [ this.value, ...arguments ])
    return this
  }
}

export default Chainable

如果可以推断出所有的库方法及其签名,那就太好了。是否可以扩展或以任何方式重用底层库中的类型?

1 个答案:

答案 0 :(得分:1)

哇,我喜欢这个问题,但我认为TypeScript还没有准备好以令人满意的方式解决它。主要的问题是,给定lib的类型,你喜欢以编程方式提取参数类型并从其方法中返回值,然后转换它们以生成类型Chainable的方法。在conditional types及其相关的type inference能力成为语言的一部分之前,这是不可行的,看起来就像它将在TypeScript v2.8中一样。

现在,这是我能得到的尽可能接近。首先,描述一个LibTemplate以一种可以编程方式转换的方式表示函数签名:

type LibTemplate = {[k: string]: { arg: any, ret: any }}

type LibFunc<L extends LibTemplate> = {
  [K in keyof L]: (arg: L[K]['arg']) => L[K]['ret']
}

因此某些类型lib的{​​{1}}将为LibFunc<Lib>。这是一个例子:

Lib

,其中

declare const lib: {
  one(arg: string): number;
  two(arg: number): boolean;
  three(arg: boolean): string;
}

您可以验证自己type Lib = { one: {arg: string, ret: number}, two: {arg: number, ret: boolean}, three: {arg: boolean, ret: string} } 的形状与LibFunc<Lib>相同。不幸的是,TypeScript不擅长从typeof lib推断Lib,并且在条件类型之类的东西可用之前不会。所以下面我将不得不明确使用typeof lib

请注意,我甚至没有尝试使用多个参数来处理函数。如果这个答案可以解决的话,我可能会考虑一下这个问题,但现在可能比它的价值更麻烦了。

让我们来看看图书馆的“正常”用途:

Lib

现在,我将描述表示将const plainResult = lib.three(lib.two(lib.one("abc"))); // string 转换为lib类所需的类型。这里的实现不是我关注的问题,而是图书馆用户看到的类型。

Chainable

所以type Chainable<L extends LibTemplate, V extends L[keyof L]['arg' | 'ret']> = { value: V } & { [K in keyof L]: (this: Chainable<L, L[K]['arg']>) => Chainable<L, L[K]['ret']> } 需要两个类型参数。 Chainable参数表示库的形状,L参数表示当前表示的V的类型。 value类型必须是其中一个库函数的参数或返回值之一。

每个V都有Chainable<L,V>类型value,以及V中每个键的一组无法方法,其类型要求当前L匹配库函数的参数类型...并返回V,其值为Chainable<L,V>

最后,这是将V转换为lib构造函数的函数签名:

Chainable

这就是我们称之为的地方。请注意,由于上述推理问题,我必须指定declare function chainify<L extends LibTemplate>( lib: LibFunc<L> ): {new <V extends L[keyof L]['arg'|'ret']>(value: V): Chainable<Lib, V>} 作为类型参数:

Lib

让我们尝试使用它......

const ChainableLib = chainify<Lib>(lib);

有效!你得到了一些类型的安全性:

const chainResult = new ChainableLib("abc").one().two().three().value; // string

(在the Playground中查看此代码)

呃,这太乱了。我不确定其他人是否有更清洁的解决方案。但这是我现在能做的最好的事情。希望它有用;祝你好运!