如何推断“代理”函数中函数参数的类型

问题描述 投票:0回答:1

我正在尝试实现一个“代理”方法,它能够动态调用该类的其他方法。一切都很顺利,直到我开始尝试其他方法参数的类型。以下示例给出

A spread argument must either have a tuple type or be passed to a rest parameter.

我做错了什么?根据 docs

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 parameters tuples
1个回答
0
投票

您遇到了 TypeScript 的已知错误/限制,如 microsoft/TypeScript#36874microsoft/TypeScript#42508 中所述(以及其中链接的问题)。如果您尝试将某些内容分散到函数调用中,TypeScript 目前似乎只允许其类型为单个元组。不支持元组的并集,或对元组进行约束泛型,或对元组求值的条件类型

如果您只想继续前进并防止错误,您可以使用 类型断言

  public callRandom(methodName: 'methodA' | 'methodB', options: [number] | [string]) {
    type optionsType = Parameters<this[typeof methodName]>;
    this[methodName](...options as [never]);
  }

因此,

[never]
是单个元组类型,因此它被视为可传播的。但为什么
never
?这是因为编译器不知道
methodName
"methodA"
还是
"methodB"
。这是两者的“结合”。所以 this[methodName] 将是函数的并集。并且函数的联合只能通过其参数类型的“交集”来安全地调用。有关该功能的说明,请参阅
TS3.3 发行说明
。由于 methodA 想要一个 number,而
methodB
想要一个
string
,因此您只能安全地传递
 
a
number
a
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

中所述重构代码。这个想法是远离联合(以及你的情况下的“重载”),转向泛型。您可以用“基本”键值类型、该类型的通用

索引
以及该类型上的映射类型的通用索引来表示您的操作。 对于您的示例,它可能看起来像 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)
是可接受的。不涉及函数的并集,也不需要参数的交集。
对于您的用例来说,这种重构可能不值得。你可能更关心权宜之计(“停止抱怨,编译器,让我继续我的一天”)而不是保护(“我需要你理解我在做什么,编译器,这样如果我犯了一个错误,你就会抓住它”)。如果是这样,那么类型断言就是正确的选择。
Playground 代码链接

© www.soinside.com 2019 - 2024. All rights reserved.