【问题标题】:Is the TypeScript type system too relaxed in this case?在这种情况下,TypeScript 类型系统是否过于宽松?
【发布时间】:2021-11-13 05:28:54
【问题描述】:

我想我遇到了一种情况,它似乎应该导致 TS 编译器出错(但事实并非如此),我希望有人能解释原因。

在下面的代码中,我将接口Foo 传递给它接受的函数frobnicator。然后,在frobnicator 的正文中,我“删除”了bar.y 字段。在frobnicator 终止后,类型系统允许我打印bar.y(没有错误),尽管y 不再存在。

类型系统不应该禁止我将foo 传递给frobnicator,因为现在foo 不再实现Foo 接口并且TS 认为它实现了吗?

interface Foo {
    bar: { x: number, y: number };
}

function frobnicator(thing: { bar: { x: number } }) {
    thing.bar = { x: 1 }; // "remove" bar.y
}

const foo: Foo = { bar: { x: 1, y: 2 } };
frobnicator(foo); // The implementation "removes" bar.y
console.log(foo.bar.y); // TypeScript does not error despite bar.y missing

【问题讨论】:

    标签: typescript type-systems structural-typing


    【解决方案1】:

    答案是肯定的,您的代码演示了 Typescript 类型系统的不健全行为,这正是您在问题中描述的原因。

    答案也是否定的,你没有“破坏” Typescript 的类型系统,因为这种不健全的行为是预期的行为。打字稿并不完全正确,也不意味着完全正确;引用the docs:

    TypeScript 的类型系统允许某些在编译时不知道的操作是安全的。当一个类型系统具有这个属性时,就说它不是“健全的”。仔细考虑了 TypeScript 允许不良行为的地方,在本文档中,我们将解释这些情况发生的位置以及它们背后的动机。

    Typescript 的设计者明确选择了稳健性和实用性之间的平衡;完全健全的类型系统不会将 {bar: {x: number, y: number}} 视为 {bar: {x: number}} 的子类型,但真正的 Javascript 代码通常会传递具有比函数实际访问的属性更多的对象,因此严格健全的行为对开发人员来说不是很有用编写逼真的 Javascript 代码的人。

    【讨论】:

      【解决方案2】:

      您的接口与函数内的代码无关,因为即使您的 const 的类型是 Foo,该函数接受的不是 foo,而是未命名的类型 { bar: { x: number } }。由于 Typescript 在 compile time 而不是在 runtime 中工作,因此它不可能知道您的函数已删除 y,因为函数内部的类型没有 y 和它外面的类型是Foo,它是函数参数类型的超类型,因此它的实例适合参数的类型。所以,一方面你的函数从对象中删除了一个字段,但是你没有告诉编译器它是被禁止的,因为函数内部的类型与你传入的类型不同,所以外部它仍然认为你有 @ 987654325@。在像 Java 这样的语言之后,这似乎违反直觉,但事实就是如此。为避免此类问题,您应该使用正确的类型,例如,在函数中使用您的接口类型。

      【讨论】:

      • “As Typescript 在编译时而不是在运行时工作”是理解 TypeScript 最重要的事情之一。
      • 我的问题是 TS 编译器在这种情况下似乎太宽松了。它很容易拒绝foo,因为它在结构上不等同于未命名类型。
      • @hgs3 如果它没有您在函数参数类型中列出的键,它将拒绝Foo,但只要它具有所有必需的键,就可以了。否则会很笨拙,因为它不允许多态性。
      • 你不能在顶级键上仍然是多态的吗?例如,假设Foo 有另一个字段baz:在这种情况下,frobnicator 接受Foo 仍然是安全和多态的,因为它所期望的只是bar,它可以保留通过确保bar 在结构上是等效的,完全安全。如果这正是 TS 的工作方式,那很好,但我确实认为即使使用更严格/更安全的类型系统,它也可以实现多态性。
      【解决方案3】:

      该函数只需要一个嵌套字典。 不是确切的密钥数量。

      【讨论】:

      • 我明白了,但是从调用站点 TypeScript 仍然是 foo 实现了 Foo 接口。我可以打印foo.bar.y 而不会出现任何错误,因为 TS 认为它存在。如果我打印Foo 上不存在的字段(例如foo.bar.z),TS 出错。
      猜你喜欢
      • 1970-01-01
      • 2011-12-03
      • 2012-05-24
      • 2020-09-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-08-07
      • 2020-02-27
      相关资源
      最近更新 更多