提问人:Marian Bazalik 提问时间:11/10/2023 更新时间:11/10/2023 访问量:28
如何在“proxy”函数中推断函数参数的类型
How to infer type of function argument in "proxy" function
问:
我正在尝试实现一个“代理”方法,该方法能够动态调用该类的其他方法。在我开始尝试其他方法参数之前,一切都很好。以下示例给出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);
}
}
答:
你遇到了 TypeScript 的已知 bug/限制,如 microsoft/TypeScript#36874 和 microsoft/TypeScript#42508 中所述(以及其中链接的问题)。如果你试图将一些东西传播到函数调用中,TypeScript 目前似乎只允许在其类型是单个元组时这样做。不支持元组的并集,或约束为元组的泛型,或计算结果为元组的条件类型。
如果只想向前推进并防止错误,可以使用类型断言:
public callRandom(methodName: 'methodA' | 'methodB', options: [number] | [string]) {
type optionsType = Parameters<this[typeof methodName]>;
this[methodName](...options as [never]);
}
因此,是单个元组类型,因此它被视为可传播的。但是为什么?这是因为编译器不知道是 is 还是 .这是两者的结合。功能的结合也是如此。函数的联合只能通过其参数类型的交集来安全地调用。有关该功能的说明,请参阅 TS3.3 发行说明。由于想要 a 和 想要 a ,你只能安全地传递同时是 a 和 a 的东西,所以你调用哪个方法并不重要。呃,但是没有这样的事情。 被简化为不可能的永不
类型。因此,编译器唯一允许您传递的就是 。[never]
never
methodName
"methodA"
"methodB"
this[methodName]
methodA
number
methodB
string
number
string
number & string
options
[never]
您可能认为 并且以这样一种方式相互关联,以便始终适合调用 ,但编译器看不到这种关联。函数的实现具有 和 作为两个独立的联合类型,因此就编译器在该实现内部所知道的而言,很有可能是 while is 。TypeScript 实际上没有对相关联合的直接支持,如 microsoft/TypeScript#30581 中所述。methodName
options
this[methodName](...options)
methodName
options
methodName
"methodA"
options
[string]
如果希望编译器真正“理解”这始终是合适的,则需要按照 microsoft/TypeScript#47109 中所述重构代码。这个想法是远离联合(在你的情况下是重载),转向泛型。您用“基本”键值类型、该类型的泛型索引以及该类型的映射类型的泛型索引来表示操作。this[methodName](...options)
对于您的示例,它可能看起来像
interface MethodParam {
methodA: [number],
methodB: [string]
}
class TestClass {
public methodA(a: number): void {
}
public methodB(b: string): void {
}
public callRandom<K extends keyof MethodParam>(
methodName: K,
options: MethodParam[K]
) {
const t: { [P in keyof MethodParam]: (...args: MethodParam[P]) => void } = this;
t[methodName](...options);
}
}
基本类型是 ,我们分配给映射类型的变量。编译器认为该赋值是可接受的(因为它遍历了 的每个属性,并且可以验证该属性是否具有适当形状的方法)。并且是泛型类型的键。并且是 、 的通用索引。MethodParam
this
t
{ [P in keyof MethodParam]: (...args: MethodParam[P]) => void }
MethodParam
TestClass
methodName
MethodParam
K
options
MethodParam
MethodParam[K]
现在是该映射类型的泛型索引,可以看作是单函数类型。由于 是 类型,则是可以接受的。不涉及函数的并集,也不需要参数的交集。t[methodName]
(...args: MethodParam[K]) => void
options
MethodParam[K]
t[methodName](...options)
对于您的用例来说,这种重构可能不值得。你可能更关心权宜之计(“别抱怨了,编译器,让我继续我的一天”),而不是保护(“我需要你明白我在做什么,编译器,这样如果我犯了一个错误,你就会抓住它”)。如果是这样,那么类型断言就是要走的路。
评论
[never]