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

Cor*_*hon 8 generics types typescript typescript4.0

在 Typescript 4.1.x 中,您可以将元组类型作为可变参数传播到函数中。

\n
type MyTuple = [string, number];\nconst myTuple: MyTuple = ["blob", 42];\n\nfunction tupleFunc(s: string, n: number): void {\n    console.log(`${s} ${n}`);\n}\n\ntupleFunc(...myTuple); // \xe2\x9c\x85 A-Ok\n
Run Code Online (Sandbox Code Playgroud)\n

Parameters<T>但是,当元组从泛型类型参数派生并使用实用程序类型时,我遇到了错误。

\n
function foo(a: number, b: boolean, c: string) {\n  return 10;\n}\n\nfoo(1, true, "baz") // 10 \xe2\x9c\x85\n\nfunction bar(...params: Parameters<typeof foo>) {\n    return foo(...params)\n}\n\nbar(1, true, "baz") // 10 \xe2\x9c\x85\n\nfunction bar2<F extends typeof foo>(...params: Parameters<F>) {\n  //  next line would work\n  //  return foo(params[0], params[1], params[2])\n\n  return foo(...params); // Fails \n  // Expected 3 arguments, but got 0 or more.ts(2556)\n  // index.ts(28, 14): An argument for \'a\' was not provided.\n}\n
Run Code Online (Sandbox Code Playgroud)\n

有没有办法让这个概念通过类型检查器或者打字稿不支持它?虽然它出错了,但它似乎可以在 Typescript 沙箱中工作。请参阅此处的示例。

\n

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

\n
function bar3<T extends typeof foo>(...params: Parameters<T>) {\n  return foo.apply(null, params);\n}\n
Run Code Online (Sandbox Code Playgroud)\n

jca*_*alz 5

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


编译器显然不确定F,无论它是什么,都会有尽可能多的参数foo,因此它会给出错误。事实证明,bar2()实施是不安全的。

TypeScript 中的可分配性规则之一是参数较少的函数可以分配给参数较多的函数。请参阅有关该主题的常见问题解答条目,了解为什么需要这样做(简短的回答:通常可以安全地假设函数将忽略额外的参数,这就是大多数人编写不需要所有传入参数的回调的方式):

const baz = () => "hello";
const assignableToFoo: typeof foo = baz; // no error
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

既然Fis typeof baz,那么Parameters<F>is [],并且bar2()可以不带参数调用,并且params可能为空。里面的错误bar2正确地警告您,这foo(...params)有潜在的危险。


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


最后,如果您决定不关心比缩短其参数列表的方式F更窄的可能性foo,那么您可以假装它params是类型Parameters<typeof foo>(事实上我认为编译器会让您不安全)将其“扩大”为该类型的变量,即使它可能不应该):

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

不过要小心!


Playground 代码链接