打字稿:将泛型函数类型参数的 Parameters<T> 元组作为可变参数传递

Typescript: pass Parameters<T> tuple of generic function type parameter as variadic arguments

在 Typescript 4.1.x 中,您可以 spread tuple types 将函数作为可变参数。

type MyTuple = [string, number];
const myTuple: MyTuple = ["blob", 42];

function tupleFunc(s: string, n: number): void {
    console.log(`${s} ${n}`);
}

tupleFunc(...myTuple); // ✅ A-Ok

但是,当元组派生自泛型类型参数并使用 Parameters<T> utility type.

时,我遇到了错误
function foo(a: number, b: boolean, c: string) {
  return 10;
}

foo(1, true, "baz") // 10 ✅

function bar(...params: Parameters<typeof foo>) {
    return foo(...params)
}

bar(1, true, "baz") // 10 ✅

function bar2<F extends typeof foo>(...params: Parameters<F>) {
  //  next line would work
  //  return foo(params[0], params[1], params[2])

  return foo(...params); // Fails 
  // Expected 3 arguments, but got 0 or more.ts(2556)
  // index.ts(28, 14): An argument for 'a' was not provided.
}

有没有办法让这个概念通过类型检查器,或者它在打字稿中不受支持?虽然它出错了,但它似乎可以在 Typescript 沙箱中工作。 See an example here.

似乎我可以让它与 .apply() 一起工作,但我很想知道是否有其他方法。

function bar3<T extends typeof foo>(...params: Parameters<T>) {
  return foo.apply(null, params);
}

这很有趣,我不知道是否存在关于它的规范 GitHub 问题。还没找到;如果我找到一个,我会回来编辑。我对错误原因的最佳猜测是函数实现中的 Parameters<F> utility type 是一个“未解析的通用条件类型”;编译器不知道 F 是什么,并且在知道之前不想承诺评估 Parameters<F> 。它不会在函数内部,除非您尝试将 params 分配给另一个变量或使用类型断言。


编译器显然不能确定 F,无论它是什么,都会有与 foo 一样多的参数,所以它给出了一个错误。事实证明 bar2() 实现是不安全的。

TypeScript 中的可分配性规则之一是参数较少的函数可分配给参数较多的函数。请参阅 the FAQ entry on the subject 以了解为什么这是可取的(简短回答:通常可以安全地假设一个函数将忽略额外的参数,这就是大多数人编写不需要所有传入参数的回调的方式):

const baz = () => "hello";
const assignableToFoo: typeof foo = baz; // no error

允许此分配的事实意味着 F extends typeof foo 可以指定您不想要的内容。想象一下 foo() 做了一些真正关心其参数类型的事情:

function foo(a: number, b: boolean, c: string): string {
  return `success ${a.toFixed(2)} ${b} ${c.toUpperCase()}`;
}

然后你可以这样调用 bar2(),根据它的定义:

console.log(bar2<typeof baz>()); // compiles fine, but:
// RUNTIME ERROR  TypeError: a.toFixed() no good if a is undefined!

因为Ftypeof baz,所以Parameters<F>[]bar2()可以不带参数调用,params可能是空的。 bar2 中的错误正确地警告您 foo(...params) 存在潜在危险。


现在因为你说这是一个简化的例子,我不是 100% 确定如何最好地编写一个捕获所需用例的 bar2 签名版本。通常泛型类型参数应该对应一些实际值;但是在调用 bar2() 时不涉及 F 类型的值,只是一个类型与其参数列表相同的值。对于编写的示例代码,我会说您应该只使用非泛型 bar().


最后,如果您决定不关心 Ffoo 更窄以缩短其参数列表的可能性,那么您可以假装paramsParameters<typeof foo> 类型(事实上,我认为编译器会让你不安全地将它“扩展”为该类型的变量,即使它可能不应该这样做):

function bar2<F extends typeof foo>(...params: Parameters<F>) {
  const _params: Parameters<typeof foo> = params;
  return foo(..._params); // no error now
}

但要小心!


Playground link to code