【问题标题】:Typescript getter type inferenceTypescript getter 类型推断
【发布时间】:2020-09-15 20:48:59
【问题描述】:

Typescript 似乎严格基于字段的私有变量来推断字段类型。 但是,如果 getter 返回类型联合 (1),它不会抱怨,也不会从 getter (2) 推断实际类型:

    test('field type inference', () => {
  class A {
    x_: number;

    // 1: no type checking here
    get x(): number | undefined {
      if (this.x_ === 1) return undefined;
      return this.x_;
    }

    set x(v: number | undefined ) {
      this.x_ = +v;
    }
  }

  const a = new A();
  a.x = 1;

  // 2: The inferred type is number (as of x_, instead of getter)
  const x: number = a.x;

  console.log(a.x) // outputs 'undefined'
})

这是指定/预期的行为吗?

稍后编辑。请注意,strictNullCheck 也不会捕捉到这一点。仅有的两个将缺少初始化和设置器

以下示例修复了strictNullCheck 警告:

test('field type inference', () => {
  class A {
    x_: number;

    get x(): number | undefined {
      if (this.x_ === 1) return undefined;
      return this.x_;
    }

    set x(v: number | undefined ) {
      this.x_ = +(v ?? 0);
    }

    constructor(value: number) {
      this.x_ = value;
    }
  }

  const a = new A(2);
  a.x = 1;

  const x: number = a.x;

  console.log(a.x)
});

【问题讨论】:

  • 您可能没有启用 strictNullChecks 作为编译器选项,在这种情况下,TS 会从联合中删除所有 undefined/null 类型。
  • 我想我知道,但是为什么这很重要,因为 strictNullCheck 只是将可能的 null 取消引用视为错误,而在这里,它不是 推断 实际类型。 IE。 strictNullCheck 没有什么可捕捉的。 strictNullCheck 将捕获的唯一一件事是我需要一个构造函数来初始化 x_。但是,如果我确实添加了构造函数,它不会再次捕获它,因为这是一个推理问题。
  • strictNullChecks不会捕获构造函数中缺少的初始化,这就是 strictPropertyInitialization 的用途。正如我所提到的,如果未设置 strictNullChecks,TypeScript 只会从任何类型联合中删除 undefined(请验证是否启用)。 getter/setter 对的类型必须相同,但完全独立于私有字段 x_
  • 我明白了。但是在实践中,确实如此。我在strictNullCheck 看到的两个错误可能是 indefined cast att setter 解决的:` set x(v: number | undefined) { this.x_ = +(v ?? 0); } ` 和缺少构造函数,用 `constructor(value: number) { this.x_ = value; 解决}` 不过我会更新帖子以澄清
  • 哦,我明白了!实际上,这看起来像是流类型中的一个错误/限制,其中对 a.x 的赋值被假定为在赋值给 x 时读取的值,但使用 getter/setter 绝对是不合理的缩小行为!您可能可以在 TypeScript Github 存储库中找到有关此行为的问题报告。

标签: typescript


【解决方案1】:

首先,当您有属性时,打字稿假定从属性中读取将检索最后写入该属性的值。你这里的 getter/setter 打破了这个约定。但是因为每个 get/set 函数在技术上都是类型安全的,所以 typescript 不会发现任何错误。

其次,当您将常量分配给类型为联合的属性时,打字稿会记住该联合的哪个成员适用于该范围的其余部分。您在这些 getter/setter 中具有逻辑的事实并不重要。

这与私有财产无关,而与公共财产有关。

假设你有一个非常简单的类:

// No getters/setters
class B {
  x: number | undefined
}

const b = new B()
b.x // number | undefined
b.x = 1
b.x // number

如您所见,typescript 会记住您分配的内容,知道它不是未定义的,然后在它知道为真的任何地方将其从属性的结果类型中删除。


现在让我们试试这个类:

// useless getters/setters
class C {
  get x(): number | undefined {
    return undefined;
  }

  set x(v: number | undefined ) {
    // no-op
  }
}

const c = new C()
c.x // number | undefined
c.x = 1
c.x // number

现在 getter/setter 没用了,但它会给你“错误”的结果。

Playground


如果最后一个值是在同一个同步执行的范围内设置的,Typescript 期望 getter 返回最后一个设置值。事实上,大多数程序员可能也会这样做。

理想情况下,编译器会在此处标记问题,但通过这些设置器跟踪类型安全实际上是一件相当复杂的事情。

【讨论】:

  • 与 TS#2521 的引用相关,.d.ts 发射在 TS 3.7 中已更改,因此不再是允许 getter/setter 具有不同类型的阻止程序(尽管该限制仍然适用今天)。请参阅 devblogs.microsoft.com/typescript/announcing-typescript-3-7 中的重大更改说明
  • 谢谢亚历克斯和@Joost!你的两个论点都是对的!如果 strictNullCheck 已经打开,原始代码会标记它(@Joost) 该示例不是因为 Second 点。
猜你喜欢
  • 2018-07-03
  • 2019-09-17
  • 1970-01-01
  • 2017-10-04
  • 1970-01-01
  • 2016-04-04
  • 2017-03-08
  • 1970-01-01
相关资源
最近更新 更多