好吧,这个标题很奇怪,但奇怪的是,你不能传递一个函数,它接受的类型实现了传入函数应该接受的接口:
interface Empty { }
class Implements implements Empty {
constructor(public s: string) {}
}
type TakesEmpty = (e: Empty) => void;
function TakesImplementor(e: Implements): void {}
function f(t: TakesEmpty) { }
/*
Argument of type '(e: Implements) => void' is not assignable to parameter of type 'TakesEmpty'.
Types of parameters 'e' and 'e' are incompatible. Property 's' is missing in type 'Empty' but required in type 'Implements'.
*/
f(TakesImplementor);
// But this works
function te (e: Empty): void {}
te(new Implements(""));
Playground链接
为什么会这样呢?
1条答案
按热度按时间nzk0hqpo1#
在你的示例代码中,运行时不会发生什么坏事,因为函数什么都不做,但是给定
TakesImplementor()
的签名,它可以像这样实现:TakesImplementor()
接受一个Implements
类型的名为e
的参数,该参数有一个string
类型的s
属性,在TakesImplementor()
中可以访问e.s.toUpperCase()
。然后,给定
f()
的签名,它可以像这样实现:传入
f()
的t
参数本身就是TakesEmpty
类型的函数,这意味着您应该能够调用t({})
,因为对象{}
是有效的Empty
。是的,一切都很好,直到你开始运行这个:
f(TakesImplementor)
调用TakesImplementor({})
,TakesImplementor({})
调用{}.s.toUpperCase()
,{}.s.toUpperCase()
并不存在,因为{}.s
是未定义的。这就是f(TakesImplementor)
行给出错误消息的原因:TakesImplementor
不是TakesEmpty
,因为TakesImplementor
只接受Implements
类型,而TakesEmpty
需要接受所有Empty
类型,而不仅仅是Implements
类型。这可能是违反直觉的,但在处理函数时,当你 * 加宽 * 它的参数类型时,你 * 缩小 * 它的类型,当你 * 缩小 * 它的参数类型时,你 * 加宽 * 它的类型。函数参数的这种“走另一条路”被称为 * convariance *。它们以相反的方式变化,或者说“contra-variant”。(这与函数 return 类型相反,它在加宽时加宽函数类型,在缩小时缩小函数类型......它们一起变化,或“共变”,因此是 * 协变 *。)
通过将
TakesImplementor
的参数类型从Empty
缩小到Implements
,您已经 * 加宽 * 了TakesImplementor
本身的类型(与TakesEmpty
相比)。每个TakesEmpty
都可以用于替换TakesImplementor
,但反之亦然。当你使用
--strictFunctionTypes
编译器标志时,函数参数类型的反变在TypeScript 2.6和更高版本中被强制执行(同样只在--strict
中被启用)。链接到代码