我有一个模块,它包装了库的所有方法,使它们可以链接,所以代替:
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
如果可以推断出所有的库方法及其签名,那就太好了。是否可以扩展或以任何方式重用底层库中的类型?
答案 0 :(得分:1)
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中查看此代码)
呃,这太乱了。我不确定其他人是否有更清洁的解决方案。但这是我现在能做的最好的事情。希望它有用;祝你好运!