function test(a: number, b: number, c: number) {}
    function test1() {}

    const o = { test, test1 };

    type Type = typeof o;

    function test2<K extends keyof Type>(key: K, ...args: Parameters<Type[K]>) {
      const fn: Type[K] = o[key];
      fn(...args);
      // err:The expansion parameter must have a tuple type or be passed to the rest parameter.
    }

fn(...args);错误:扩展参数必须具有元组类型或传递给REST参数.

我该怎么办?

推荐答案

如果你只是想尽快跳过错误,你可以使用the any type来禁用fn的类型判断:

function test2<K extends keyof Type>(key: K, ...args: Parameters<Type[K]>) {
    // TS can't figure out the relationship, just mark fn as any
    const fn: any = o[key];
    fn(...args); // okay
}

当然,您完全放弃编译器验证的类型安全是为了方便.当使用anytype assertions时,你将类型判断的责任从编译器中剥离出来.


另一方面,如果您希望编译器理解您正在做的事情的逻辑,那么您需要将o的类型重构为在类型级别上实际表示它的某种东西.类型判断器不知道Parameters<Type[K]>对于类型Type[K]的函数是合适的参数.类型Type[K]被视为被约束到union个函数,而不是单个generic个函数类型.类型判断器不能将表单{ test(a: number, b: number, c: number): void; test2(): void }抽象为任何有用的内容.类型Parameters<Type[K]>是泛型conditional type,并且这样的类型对于编译器来说是出了名的难以理解.它没有将意义赋予识别符"Parameters",也不能抽象地推理出这一点.

打字脚本不能推断数据 struct 之间的抽象相关性这一普遍问题在microsoft/TypeScript#30581中讨论过,在那里它是根据相关unions来表述的.对此支持的解决方案和推荐的方法是将通用indexed accesses的特定形式重构为mapped type.这在microsoft/TypeScript#47109中有详细的描述.

对于这里的示例代码,必要的重构如下所示

function test3<K extends keyof Type>(key: K, ...args: Parameters<Type[K]>) {
    const _o: { [P in keyof Type]: (...args: Parameters<Type[P]>) => void } = o;
    const fn = _o[key];
    fn(...args); // okay
}

这里我们将o赋值给映射类型{ [P in keyof Type]: (...args: Parameters<Type[P]>) => void }的新变量_o. 这个赋值成功是因为当给定一个非泛型值o时,编译器会完全计算_o的类型,并发现它们在 struct 上是相同的.

但即使它们在 struct 上是相同的,它们也有不同的表现形式.映射类型显式捕获任意属性_o与其函数形状之间的泛型关系. 用key索引到_o并不被视为函数的联合,而是直接作为单个函数类型(...args: Parameters<Type[K]>) => void. 这个函数类型很容易接受...args.

这就是TypeScrip如何在保持一定的类型安全保证的情况下支持这种功能.这种特殊的重构不需要花费太多精力或添加代码,但通常这种重构会使需要维护代码的人类开发人员更难理解代码.这取决于您的用例,快速/轻松/不安全和缓慢/复杂/安全中的哪一个更可取.


Playground link to code

Typescript相关问答推荐

Angular 17 -如何在for循环内创建一些加载?

对深度对象键/路径进行适当的智能感知和类型判断,作为函数参数,而不会触发递归类型限制器

键集分页顺序/时间戳上的筛选器,精度不相同

类型脚本类型字段组合

使用`renderToPipeableStream`时如何正确恢复服务器端Apollo缓存?

如何将所有props传递给React中的组件?

Angular NgModel不更新Typescript

为什么不能使用$EVENT接收字符串数组?

是否将自动导入样式从`~/目录/文件`改为`@/目录/文件`?

刷新页面时,TypeScrip Redux丢失状态

从非文字数组(object.keys和array.map)派生联合类型

类型脚本-在调用期间解析函数参数类型

埃斯林特警告危险使用&as";

基于属性值的条件类型

如何键入';v型';

传入类型参数<;T>;变容函数

Typescript泛型-键入对象时保持推理

元组解释

如何在没有空接口的情况下实现求和类型功能?

const 使用条件类型时,通用类型参数不会推断为 const