为什么 Parameters<Func> extend unknown[] (Array<unknown>) in ts strict mode

Why don't Parameters<Func> extend unknown[] (Array<unknown>) in ts strict mode

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

  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[] .由于 return 类型始终是数字,我不明白伪造 extends 语句还有什么可能不同。

其他说明:

type testIV = testArgsF extends unknownArgsF ? true : false; // false 
type testIVa = unknownArgsF extends testArgsF  ? true : false; // true!

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

扩展实际上意味着 'is assignable to'。 如果B可分配给A,则f(p:A)=>可分配给f(p:B)=>。我认为它被称为'contravariant'。

// Imagine your function expects callback that has some type as a paramter:

var f = (cb: ({ a, b }: { a: number, b: number }) => void) => { cb({ a:1, b: 1}); };

// you can safely call it giving it the function that accepts parameter 
// that the original parameter is assignable to but not the other way around
// because that's the way to guarantee that when our function f calls the callback
// it will provide sufficient amount of data
f(({ a }: { a: number }) => { })


var x = { a: 1, b: 1 };
var y = { a: 1 }

x = y; // error because x would have no b property after that assignment
y = x; // works because y has all it needs after that assignment and it doesn't matter it has property b too

// in other words:
type F = { a: number } extends { a: number, b: number } ? true : false; // false
type T = { a: number, b: number } extends { a: number } ? true : false; // true

// read 'extends' as 'is assignable to'

启用 --strictFunctionTypes compiler option 后,将逆变式 检查函数类型参数。 "Contra-variant"表示函数的子类型关系与函数参数的子类型关系变化方向相反。因此,如果 A extends B,则 (x: B)=>void extends (x: A)=>void 而不是相反

由于 TypeScript 中 "substitutability"(也称为 behavioral subtyping)的性质,这是一个类型安全问题。如果 A extends B 为真,您应该能够将 A 用作 B。如果不能,则 A extends B 不正确。

如果您关闭 --strict,那么编译器会使用 TS-2.6 之前的行为来检查函数参数 双变量 ,这是不安全的,但由于某些原因被允许的生产力。这在这里可能是题外话,但您可以在 "Why are function parameters bivariant?"

的 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 而不会出错,当 u 愉快地接受一个时,会立即导致运行时错误非 string 第二个参数。

您可以看到 subtype/implement 函数类型的唯一安全方法是 subtype/implementation 接受相同或 更宽 的参数比 supertype/call-signature 预期的要多。这就是 --strictFunctionTypes 被引入该语言的原因。


如果您将 unknown 更改为 any(使用 anyArgsF 而不是 unknownArgsF),那么编译器将不会报错 因为 any 在 TypeScript 中是 。类型 any 被认为可以分配给 tofrom 其他类型;这是不安全的,因为例如 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!

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

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

TypeScript FAQ 条目中对此进行了解释 "Why are functions with fewer parameters assignable to functions that take more parameters?"


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

Playground link to code

我会尽量简单地说:"extends" -> "compatible" -> "can be used in place of"

你能用吗(arg1: boolean, arg2: string) => number;
而不是 (...args: unknown[]) => number

不,因为后者可以处理没有参数的调用,但前者可能会在运行时失败(例如将尝试访问参数的属性)

有关功能兼容性的更多信息here