首页 > 解决方案 > 为什么没有参数扩展未知[](数组) 在 ts 严格模式下

问题描述

标题几乎说明了一切。我有这个代码:

  type testNoArgsF = () => number;
  type testArgsF = (arg1: boolean, arg2: string) => number;
  type unknownArgsF = (...args: unknown[]) => number;
  type anyArgsF = (...args: any[]) => number;

  type testII = testArgsF extends anyArgsF ? true : false; // true
  type testIII = Parameters<testArgsF> extends Parameters<unknownArgsF>
    ? true
    : false; // true

  // unexpected:
  type testIV = testArgsF extends unknownArgsF ? true : false; // false <- why?
  // even though:
  type testV = testNoArgsF extends unknownArgsF ? true : false; // true

它是用打字稿(3.8 版)编写的,我启用了严格模式。出乎意料的结果是测试函数没有扩展带有扩展参数的函数类型unknown[],但是如果您只是检查它们确实扩展的参数unknown[]。由于返回类型始终是数字,我不明白还有什么不同的地方可以伪造extends语句。

其他注意事项:

标签: typescripttypesargumentsextendsspread-syntax

解决方案


--strictFunctionTypes启用编译器选项后,会以逆变方式检查函数类型参数。“逆变”是指函数的子类型关系与函数参数的子类型关系的变化方向相反。因此,如果A extends B,则(x: B)=>void extends (x: A)=>void 反之亦然

由于 TypeScript 中的“可替代性”(也称为行为子类型化)的性质,这是一个类型安全问题。如果A extends B为真,您应该能够将 anA用作B. 如果你不能,那就A extends B不是真的。

如果您关闭,--strict那么编译器会使用 TS-2.6 之前的行为来检查函数参数bivariantly,这是不安全的,但出于生产力的原因是允许的。这可能是题外话,但您可以在 TypeScript 常见问题解答条目“为什么函数参数是双变量的?”中阅读更多相关信息。


无论如何,如果你需要一个接受任何数字unknown参数的函数类型,你不能安全地使用一个只有特定子类型的函数unknown。观察:

const t: testArgsF = (b, s) => (b ? s.trim() : s).length
const u: unknownArgsF = t; // error!

u(1, 2, 3); // explosion at runtime! s.trim is not a function

如果testArgsF extends unknownArgsF为真,那么您将能够在没有错误的情况下分配t给上面,当愉快地接受非第二个参数 u时立即导致运行时错误。ustring

您可以看到子类型/实现函数类型的唯一安全方法是子类型/实现接受与超类型/调用签名预期的参数相同或更宽的参数。这就是为什么--strictFunctionTypes被引入该语言的原因。


如果您更改unknownany(使用anyArgsF而不是unknownArgsF),则编译器不会抱怨,因为在 TypeScriptany故意不正确。该类型any被认为可以分配其他所有类型,也可以分配其他类型;这是不安全的,因为例如string extends anyany extends number都是真的而string extends number都是假的。因此,上述替代原则在any涉及时不强制执行。将值注释为any类型相当于放松或关闭对该值的类型检查。这并不能使您免于运行时错误;它只是消除了编译器的错误:

const a: anyArgsF = t; // okay, type checking with any is disabled/loosened
a(1, 2, 3); // same explosion at runtime!

在 wheretestNoArgsF extends unknownArgsF为真的情况下,这也是可替代性的结果。您可以使用不带参数的函数,就好像它几乎是任何函数类型一样,因为它(通常)最终会忽略传递给它的任何参数:

const n: testNoArgsF = () => 1;
const u2: unknownArgsF = n; // okay
u2(1, 2, 3); // okay at runtime, since `n` ignores its arguments

这在 TypeScript 常见问题解答条目“为什么具有较少参数的函数可分配给采用更多参数的函数?”中进行了解释。.


好的,希望有帮助;祝你好运!

Playground 代码链接


推荐阅读