如果作为参数传递,函数签名不会自动加宽
Function signature is not automatically widened if passed as an argument
我正在尝试创建一个接受大量具有特定签名的函数的高阶函数。在这种情况下,签名是任何函数,它只接受一个实现特定接口的参数。说,Foo
:
interface Foo {
foo: string
}
function higherOrderStuff(funs: ((foo: Foo) => void)[]) {
// do higher order stuff
}
但是,为了便于类型检查,一些函数使用了原始接口的intersections。像这样:
type MyFoo = Foo & {
foo: 'bar'
};
function myFunction(foo: MyFoo) {
// do my stuff
}
不幸的是,将这些函数传递给高阶包装器会产生错误:
higherOrderStuff([myFunction]); // compiler error
TS2322: Type '(foo: MyFoo) => void' is not assignable to type '(foo: Foo) => void'.
Types of parameters 'foo' and 'foo' are incompatible.
Type 'Foo' is not assignable to type 'MyFoo'.
Type 'Foo' is not assignable to type '{ foo: "bar"; }'.
Types of property 'foo' are incompatible.
Type 'string' is not assignable to type '"bar"'.
错误是正确的:Foo
无法分配给 MyFoo
。事实上,我做相反的事情是有原因的。
更奇怪的是,如果我尝试不同的东西并传递函数以外的任何东西,相同的场景会按预期工作。
function doStuffWithFoos(foos: Foo[]) {
// do stuff
}
const myFoo: MyFoo = { foo: 'bar' };
doStuffWithFoos([myFoo]); // no squiggly line
那么,为什么参数被正确加宽,而函数参数的参数却没有?
显式转换修复了错误,但我似乎不需要它。
higherOrderStuff([myFunction as (foo: Foo) => void]); // this works
编译器错误的发生是因为函数在它们的参数类型中是逆变,这基本上意味着如果您传递的函数采用T
类型的参数,您不能传递一个函数,该函数采用 T
子类型的参数(不过超类型很好)。
作为代码中的示例,假设您通过将对象 {foo: 'not-bar'}
传递给 funs
:
中的每个函数来实现 higherOrderStuff
interface Foo {
foo: string
}
function higherOrderStuff(funs: ((foo: Foo) => void)[]) {
return funs.map(f => f({foo: 'not-bar'}))
}
由于 MyFoo
具有较窄的类型 {foo: bar}
由于
type MyFoo = Foo & {
foo: 'bar'
};
您可以定义一个 myFunction
取决于其参数具有这种更窄的类型:
function myFunction(foo: MyFoo) {
const shouldBeBar = foo.foo // inferred type: 'bar'
const barObject = {bar: () => 42}
const barFunction = barObject[shouldBeBar]
return barFunction() // should return 42
}
一切仍然类型正确,因为 shouldBeBar
只能是 bar
,barFunction
将是一个返回 42 的函数。
但是如果你现在 运行
console.log(higherOrderStuff([myFunction]))
它最终会调用 myFunction({foo: 'not-bar'})
,这将导致 barFunction
结束 undefined
并引发 运行 时间错误。
这是类型错误试图避免发生的事情。
要理解协变和逆变,水果和榨汁机的类比可能会有所帮助。
如果你要一块水果,得到亚型橙子就可以了,而如果你要一个橙子,你不只是想得到任何一种水果。这是协方差。
现在考虑你需要一个普通的果汁机(即从水果到果汁的功能),那么买一个橙子榨汁机就不行了,因为你可能还想榨菠萝汁。另一方面,如果您要橙子榨汁机,那么普通的果汁机就可以了。你可以说榨汁机在榨汁的类型上是逆变的。
我正在尝试创建一个接受大量具有特定签名的函数的高阶函数。在这种情况下,签名是任何函数,它只接受一个实现特定接口的参数。说,Foo
:
interface Foo {
foo: string
}
function higherOrderStuff(funs: ((foo: Foo) => void)[]) {
// do higher order stuff
}
但是,为了便于类型检查,一些函数使用了原始接口的intersections。像这样:
type MyFoo = Foo & {
foo: 'bar'
};
function myFunction(foo: MyFoo) {
// do my stuff
}
不幸的是,将这些函数传递给高阶包装器会产生错误:
higherOrderStuff([myFunction]); // compiler error
TS2322: Type '(foo: MyFoo) => void' is not assignable to type '(foo: Foo) => void'.
Types of parameters 'foo' and 'foo' are incompatible.
Type 'Foo' is not assignable to type 'MyFoo'.
Type 'Foo' is not assignable to type '{ foo: "bar"; }'.
Types of property 'foo' are incompatible.
Type 'string' is not assignable to type '"bar"'.
错误是正确的:Foo
无法分配给 MyFoo
。事实上,我做相反的事情是有原因的。
更奇怪的是,如果我尝试不同的东西并传递函数以外的任何东西,相同的场景会按预期工作。
function doStuffWithFoos(foos: Foo[]) {
// do stuff
}
const myFoo: MyFoo = { foo: 'bar' };
doStuffWithFoos([myFoo]); // no squiggly line
那么,为什么参数被正确加宽,而函数参数的参数却没有?
显式转换修复了错误,但我似乎不需要它。
higherOrderStuff([myFunction as (foo: Foo) => void]); // this works
编译器错误的发生是因为函数在它们的参数类型中是逆变,这基本上意味着如果您传递的函数采用T
类型的参数,您不能传递一个函数,该函数采用 T
子类型的参数(不过超类型很好)。
作为代码中的示例,假设您通过将对象 {foo: 'not-bar'}
传递给 funs
:
higherOrderStuff
interface Foo {
foo: string
}
function higherOrderStuff(funs: ((foo: Foo) => void)[]) {
return funs.map(f => f({foo: 'not-bar'}))
}
由于 MyFoo
具有较窄的类型 {foo: bar}
由于
type MyFoo = Foo & {
foo: 'bar'
};
您可以定义一个 myFunction
取决于其参数具有这种更窄的类型:
function myFunction(foo: MyFoo) {
const shouldBeBar = foo.foo // inferred type: 'bar'
const barObject = {bar: () => 42}
const barFunction = barObject[shouldBeBar]
return barFunction() // should return 42
}
一切仍然类型正确,因为 shouldBeBar
只能是 bar
,barFunction
将是一个返回 42 的函数。
但是如果你现在 运行
console.log(higherOrderStuff([myFunction]))
它最终会调用 myFunction({foo: 'not-bar'})
,这将导致 barFunction
结束 undefined
并引发 运行 时间错误。
这是类型错误试图避免发生的事情。
要理解协变和逆变,水果和榨汁机的类比可能会有所帮助。
如果你要一块水果,得到亚型橙子就可以了,而如果你要一个橙子,你不只是想得到任何一种水果。这是协方差。
现在考虑你需要一个普通的果汁机(即从水果到果汁的功能),那么买一个橙子榨汁机就不行了,因为你可能还想榨菠萝汁。另一方面,如果您要橙子榨汁机,那么普通的果汁机就可以了。你可以说榨汁机在榨汁的类型上是逆变的。