我试图实现一个“代理”方法,它能够动态调用类的其他方法。在我开始尝试其他方法参数类型之前,一切都很好。下面的例子给出了A spread argument must either have a tuple type or be passed to a rest parameter.
我做错了什么?根据文档,Parameters
实用函数返回元组:(
class testClass {
public methodA(a: number): void {
}
public methodB(b: string): void {
}
public callRandom(methodName: 'methodA', options: [number]): void;
public callRandom(methodName: 'methodB', options: [string]): void;
public callRandom(methodName: 'methodA' | 'methodB', options: [number] | [string]) {
type optionsType = Parameters<this[typeof methodName]>;
this[methodName](...options as optionsType);
}
}
字符串
操场
1条答案
按热度按时间hgtggwj01#
您遇到了TypeScript的一个已知错误/限制,在microsoft/TypeScript#36874和microsoft/TypeScript#42508(以及其中链接的问题)中进行了描述。如果您试图将某些内容扩展到函数调用中,TypeScript目前似乎只允许其类型为单个元组。不支持元组的联合,或限制为元组的generics,或计算为元组的conditional types。
如果你只是想继续前进并防止错误,你可以使用类型Assert:
字符串
所以,
[never]
是一个单一的元组类型,所以它被看作是可扩展的。但是为什么是never
呢?这是因为编译器不知道methodName
是"methodA"
还是"methodB"
。因此this[methodName]
将是函数的联合。并且函数的联合只能通过其参数类型的交集来安全地调用。请参见由于methodA
需要一个number
,而methodB
需要一个string
,所以你只能安全地传递一个同时是number
和string
的东西,所以你调用的是哪一个方法并不重要。但是没有这样的东西。number & string
被简化为不可能的never
类型。因此编译器唯一允许你传递的options
是[never]
。你可能会认为
methodName
和options
是相互关联的,所以调用this[methodName](...options)
总是合适的,但是编译器看不到这种关联。你的函数的实现有methodName
和options
作为两个独立的联合类型,所以编译器在实现中知道,methodName
很可能是"methodA"
,而options
是[string]
。TypeScript实际上并不直接支持相关的联合,如microsoft/TypeScript#30581中所述。如果你想让编译器真正“理解”
this[methodName](...options)
总是合适的,那么你需要像microsoft/TypeScript#47109中描述的那样重构你的代码。你用“基本”键值类型来表示你的操作,用泛型indexes来表示这个类型,以及对该类型的mapped types的泛型索引。对于你的例子,它可能看起来像
型
基本类型为
MethodParam
,我们将this
赋值给Map类型为{ [P in keyof MethodParam]: (...args: MethodParam[P]) => void }
的变量t
。编译器认为该赋值是可接受的(因为它遍历了MethodParam
的每个属性,并且可以验证TestClass
在该属性处具有适当形状的方法)methodName
是MethodParam
的一个键,其泛型类型为K
。options
是MethodParam
,MethodParam[K]
的泛型索引。现在
t[methodName]
是该Map类型的泛型索引,可以看作单个函数类型(...args: MethodParam[K]) => void
。由于options
是MethodParam[K]
类型,因此t[methodName](...options)
是可接受的。不涉及函数的联合,也不需要参数的交集。这种重构对于你的用例来说可能不值得。你可能更关心的是权宜之计(“停止抱怨,编译器,让我继续我的一天”)而不是保护(“我需要你理解我在做什么,编译器,这样如果我犯了错误,你会发现它”)。如果是这样,那么类型Assert是一条路要走。
Playground代码链接