为什么 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
语句还有什么可能不同。
其他说明:
- 仅当您的测试函数有 0 个参数时,extend 语句才为真。
- 如果关闭严格模式,则不会出现此行为。
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
被认为可以分配给 to 和 from 其他类型;这是不安全的,因为例如 string extends any
和 any 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?"。
好的,希望对您有所帮助;祝你好运!
我会尽量简单地说:"extends" -> "compatible" -> "can be used in place of"
你能用吗(arg1: boolean, arg2: string) => number;
而不是 (...args: unknown[]) => number
?
不,因为后者可以处理没有参数的调用,但前者可能会在运行时失败(例如将尝试访问参数的属性)
有关功能兼容性的更多信息here
标题几乎说明了一切。我有这个代码:
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
语句还有什么可能不同。
其他说明:
- 仅当您的测试函数有 0 个参数时,extend 语句才为真。
- 如果关闭严格模式,则不会出现此行为。
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?"
无论如何,如果您需要接受任意数量 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
被认为可以分配给 to 和 from 其他类型;这是不安全的,因为例如 string extends any
和 any 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?"。
好的,希望对您有所帮助;祝你好运!
我会尽量简单地说:"extends" -> "compatible" -> "can be used in place of"
你能用吗(arg1: boolean, arg2: string) => number;
而不是 (...args: unknown[]) => number
?
不,因为后者可以处理没有参数的调用,但前者可能会在运行时失败(例如将尝试访问参数的属性)
有关功能兼容性的更多信息here