首页 > 解决方案 > 是否可以概括一个函数并在打字稿中捕获它的签名?

问题描述

我想创建一个函数,该函数采用一组函数(都具有相同的签名)并返回一个具有相同签名的函数,然后依次运行每个函数。像这样:

function runAll<F extends (...args: any[]) => void>(...funcs: F[]): F {
  return ((...args: any[]) => {
    funcs.forEach(f => {
      f.apply(null, args);
    });
  }) as F;
}

代码编译并运行良好。但是,如果我接受as F演员表,打字稿编译器会拒绝以下代码:

Type '(...args: any[]) => void' is not assignable to type 'F'.
  '(...args: any[]) => void' is assignable to the constraint of type 'F', but 'F' could be instantiated with a different subtype of constraint '(...args: any[]) => void'.ts(2322)

一般而言,是否可以捕获函数的签名?

标签: typescript

解决方案


函数类型扩展与标准类型扩展不同,与标准类型一样,我们有协变,所以我们可以使用给定类型的子类型,对于函数类型,这是参数的逆变和返回类型的协变。

这意味着扩展另一种函数类型的函数类型可以具有与扩展的函数类型相同或更少的参数,并且参数将具有更广泛的类型(逆变)和给定类型的子类型作为回报(协方差)。例如,如果我们有函数(a: "abc", b: number) => string,那么扩展它的函数就是(a: string) => "abc". 确切地说,它的参数较少,并且 return 是字符串的子类型。同样在常识中它是正确的,因为我们可以将缺少参数理解为只是简单地跳过它们。我们甚至可以在这个简单的例子中进行测试:

type F = (a: "abc", b: number) => string
type G = (a: string) => "abc"

type isGExtendsF = G extends F ? true : false; // evaluates into true

F如果我们将 type 函数传递给它,看看你的泛型是如何表现的() => number

runAll(() => 1, () => 2); // F is inferred as () => number

显然() => number不是我们想要返回的类型(...args: any[]) => void,它是更窄的类型。


如果我们所有的函数都具有相同的确切参数类型,我从你的函数体中理解,那么我们可以进入以下专注于参数的类型:

function runAll<A extends any[]>(...funcs: ((...args: A) => void)[]): (...args: A) => void {
  return ((...args: A) => {
    funcs.forEach(f => {
      f.apply(null, args);
    });
  });
}

现在我们阻止使用具有不同类型参数的函数。例如这样的代码:

runAll((a: number) => a, (b: string) => string); // error functions needs to have the same type

推荐阅读