Typescript 如果作为参数传递,函数签名不会自动加宽
我试图创建一个高阶函数,它接受许多具有特定签名的函数。在本例中,签名是只接受一个实现特定接口的参数的任何函数。说,Typescript 如果作为参数传递,函数签名不会自动加宽,typescript,type-conversion,signature,Typescript,Type Conversion,Signature,我试图创建一个高阶函数,它接受许多具有特定签名的函数。在本例中,签名是只接受一个实现特定接口的参数的任何函数。说,Foo: 接口Foo{ foo:string } 函数higherOrderStuff(funs:((foo:foo)=>void)[]{ //做高阶的工作 } 但是,为了便于类型检查,有些函数使用原始接口的交点。像这样: 键入MyFoo=Foo&{ 福:“酒吧” }; 函数myFunction(foo:MyFoo){ //做我的事 } 不幸的是,将这些函数传递给高阶包装会产生错
Foo
:
接口Foo{
foo:string
}
函数higherOrderStuff(funs:((foo:foo)=>void)[]{
//做高阶的工作
}
但是,为了便于类型检查,有些函数使用原始接口的交点。像这样:
键入MyFoo=Foo&{
福:“酒吧”
};
函数myFunction(foo:MyFoo){
//做我的事
}
不幸的是,将这些函数传递给高阶包装会产生错误:
higherOrderStuff([myFunction]);//编译错误
错误是正确的:Foo
不能分配给MyFoo
。事实上,我这样做是有原因的
更奇怪的是,如果我尝试了一些不同的东西,并传递了函数以外的任何东西,那么同样的场景也会像预期的那样工作
函数dostufwithfoos(foos:Foo[]){
//做事
}
常量myFoo:myFoo={foo:'bar'};
dostufwithfoos([myFoo]);//没有弯弯曲曲的线条
那么,为什么参数被正确地加宽了,而函数参数的参数却没有
显式强制转换修复了错误,但我似乎不需要它
higherOrderStuff([myFunction as(foo:foo)=>void]);//这很有效
编译器错误的发生是因为函数的参数类型是反变的,这基本上意味着,如果传递的函数的参数类型为T
,则不能传递的函数的参数类型为T
(不过,超类型也可以)
作为代码中的一个示例,假设您通过向funs
中的每个函数传递对象{foo:'not bar'}
来实现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
}
所有内容的类型仍然正确,并且由于shouldbebebar
只能是bar
,barFunction
将是一个返回42的函数
但是如果你现在跑
console.log(higherOrderStuff([myFunction]))
它将调用myFunction({foo:'notbar'})
,这将导致barFunction
以undefined
结束,并引发运行时错误
这就是类型错误试图防止发生的情况
为了理解协变和逆变,水果和果汁机的类比可能会有所帮助
如果你要一片水果,可以得到一个亚型的橘子,而如果你要一个橘子,你不只是想得到任何种类的水果。这是协方差
现在你需要一个普通的榨汁机(即水果到果汁的一个功能),那么买一个橙汁榨汁机是不好的,因为你可能也想要菠萝汁。另一方面,如果你要一台橙汁机,普通的果汁机也可以。你可以说榨汁机榨汁的东西的类型是反变的。编译器错误的发生是因为函数的参数类型是反变的,这基本上意味着如果你传递的函数采用的是
T
类型的参数,您不能传递一个采用子类型t
的参数的函数(尽管是超类型也可以)
作为代码中的一个示例,假设您通过向funs
中的每个函数传递对象{foo:'not bar'}
来实现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
}
所有内容的类型仍然正确,并且由于shouldbebebar
只能是bar
,barFunction
将是一个返回42的函数
但是如果你现在跑
console.log(higherOrderStuff([myFunction]))
它将调用myFunction({foo:'notbar'})
,这将导致barFunction
以undefined
结束,并引发运行时错误
这就是类型错误试图防止发生的情况
为了理解协变和逆变,水果和果汁机的类比可能会有所帮助
如果你要一片水果,可以得到一个亚型的橘子,而如果你要一个橘子,你不只是想得到任何种类的水果。这是协方差
现在你需要一个普通的榨汁机(即水果到果汁的一个功能),那么买一个橙汁榨汁机是不好的,因为你可能也想要菠萝汁。另一方面,如果你要一台橙汁机,普通的果汁机也可以。你可以说榨汁机榨汁的东西类型是相反的。你能推荐一篇关于协方差和逆变的文章或书吗?这对我来说很神奇,我只是凭直觉理解level@captain-不幸的是,我从来没有找到一个很好的解释。通常它们相当抽象或过于冗长。这个还行:谢谢,我已经读过这篇文章了,甚至在我的书签里都有)谢谢你的详细回答。我不知道协方差。现在它变得更有意义了。但是,如果基于我的要求,我认为对冲检查妨碍了我的工作,是否可以在高阶函数上禁用它们,而不是在参数级别(使用强制转换)解决问题?我希望保留“strictFunctionTypes”标志,因为这通常是理想的行为。如果话题太大,无法发表评论,我愿意开始一个新问题section@fedetibaldo我还想到一件事:你也可以