为什么这在Typescript 中不起作用?

class Parent {
    id: string = ''
}

class Child extends Parent{
    name: string = ''
}



const fails: (created: Parent) => void = (created: Child) => { return };
const failsToo: ({ created }: { created: Parent }) => void = ({ created }: { created: Child }) => { return };

至少对我来说,这个错误非常奇怪:

Type '(created: Child) => void' is not assignable to type '(created: Parent) => void'.
  Types of parameters 'created' and 'created' are incompatible.
    Property 'name' is missing in type 'Parent' but required in type 'Child'

它看起来像是在try assign a Parent to a Child,但在真正的代码中是向后的(正在try assign a method parameter that is a Child to a Parent.这是有意义的,因为子代是父代的超集)

我是不是遗漏了什么?

我们可以在这里进行验证:https://www.typescriptlang.org/play?#code/MYGwhgzhAEAKYCcCmA7ALtA3gKGn6AlgCYBc0EaCBKA5tALzQDkT2AvttqJDAMIAWBEEWhIAHmlREY8ZOhz5oKMAFskZClVoNmrDpy4B7FBWgAzMEIhkAFMGRhJpOIlRoAlAwB80AG6HiHTsHJzIBISJPeh9MaGQ0AFcEFGg2AG4jEwwLKwAVQ0NbWPskRyQRNjJikPKyWTdUqJ9-QMYbatKnVKroErLncOFG7yw4pETk1LSgA

推荐答案

函数类型的参数类型为of type theory15/difference-between-variance-covariance-contravariance-and-bivariance-in-typesc">contravariant.counter-vary型.它们的变化方向是相反的.因此,如果是A extends B,那么((x: B)=>void) extends ((x: A)=>void),而不是((x: A)=>void) extends ((x: B)=>void).这是一个自然的结果of type theory,但您可以通过想象try 向函数传递比它们预期的更窄/更宽的类型并看看会发生什么,来说服自己相信这一必要性.例如,假设这是成功的:

const fails: (created: Parent) => void =
    (created: Child) => { created.name.toUpperCase() };

函数(created: Child) => { created.name.toUpperCase() }本身很好;它接受Child并访问其name属性(即string),因此它有一个toUpperCase()方法.但是您已经将它赋给了一个类型为(created: Parent) => void的变量.这意味着你可以这样拨打fails():

fails(new Parent()); // okay at compile time, but
// ???? RUNTIME ERROR! created.name is undefined

如果fails()接受任何Parent,那么您可以向它传递new Parent(),而不是Child.但是现在您有了一个运行时错误,因为在运行时您试图访问您提供的Parent实例的不存在的name属性的toUpperCase()方法.哎呀,我们在什么地方搞错了.

而错误恰恰在于,你不能对函数期望的类型进行widen.如果您启用了the --strictFunctionTypes compiler option,则TypeScrip将有助于报告这种不正确的参数加宽的错误,这是推荐的.


您不能加宽参数类型,但可以将其设置为narrow.如果你有一个GrandChild的子类,你就可以毫无问题地做到这一点:

class GrandChild extends Child {
    age: number = 1;
}
const okay: (created: GrandChild) => void =
    (created: Child) => { created.name.toUpperCase() };

okay(new GrandChild()); // okay

这很好,因为okay()只能接受GrandChild个实例,每个实例也是Child个实例,这是函数实现所期望的.该实现不知道它被赋予了GrandChildren,因此它不能访问age属性,但这样做是安全的.

经验法则是,当一个类型出现在input位置时,就像函数参数一样,类型变化的方向与它在output位置的方向相反.

Playground link to code

Javascript相关问答推荐

Flutter:显示PDF和视频,但阻止下载

我的YouTube视频没有以html形式显示,以获取免费加密信号

如何通过onClick为一组按钮分配功能;

是否有方法在OpenWeatherMap API中获取过go 的降水数据?

我在这个黑暗模式按钮上做错了什么?

被CSS优先级所迷惑

如何从JSON数组添加Google Maps标记—或者如何为数组添加参数到标记构造器

未捕获错误:在注销后重定向到/login页面时找不到匹配的路由

如何将Map字符串,布尔值转换为{key:string;value:bo布尔值;}[]?<>

html + java script!需要帮助来了解为什么我得到(无效的用户名或密码)

编剧如何获得一个div内的所有链接,然后判断每个链接是否都会得到200?

在执行异步导入之前判断模块是否已导入()

编辑文本无响应.onClick(扩展脚本)

如何在Angular拖放组件中同步数组?

为什么我的按钮没有从&q;1更改为&q;X&q;?

Phaserjs-创建带有层纹理的精灵层以自定义外观

更新Redux存储中的对象数组

FireBase FiRestore安全规则-嵌套对象的MapDiff

我无法在Api Reaction本机上发出GET请求

在高位图中显示每个y轴系列的多个值