【问题标题】:TypeScript 'number' not assignable to 0 | 10 | 20 even if number is 0TypeScript 'number' 不可分配给 0 | 10 | 20 即使数字为 0
【发布时间】:2019-06-07 15:27:38
【问题描述】:

如果您在下面的the typescript playground 上尝试我的 sn-p,它会在第 24 行显示错误。

我意识到可以通过再次将命令显式转换为 <IPlan[]> 来修复它,但为什么有必要这样做?

Property 'commands' in type 'Thing' is not assignable to the same property in base type 'AbstractThing'.
  Type '{ plan: number; }[]' is not assignable to type 'IPlan[]'.
    Type '{ plan: number; }' is not assignable to type 'IPlan'.
      Types of property 'plan' are incompatible.
        Type 'number' is not assignable to type '0 | 10 | 20'.
(property) Thing.commands: { plan: number; }[]

这是sn-p:

interface IPlan {
  plan: 0 | 10 | 20;
}

// fine
let obj: IPlan = {
  plan: 0
};

// also fine
let commands: IPlan[] = [
  {
    plan: 10
  }
];

// not fine
abstract class AbstractThing {
  commands?: IPlan[];
}

class Thing extends AbstractThing {
  // this line has the error
  commands = [
    {
      plan: 10
    }
  ]
}

【问题讨论】:

标签: javascript typescript


【解决方案1】:

不知道为什么,但是使用抽象类使它认为plan 的定义是number,而不是您列出的具体值。

这两个“解决方法”将有助于确保使用正确的类型

class Thing extends AbstractThing {
  commands = [
    {
      plan: 10
    }
  ] as IPlan[]
}

class Thing extends AbstractThing {
  commands: IPlan[] = [
    {
      plan: 10
    }
  ]
}

【讨论】:

  • "strongly" 指定这样的类型是我所看到的推荐方法。我会选择这个。
【解决方案2】:

这是一个known issue,其中的属性(和方法,在different issue 中提到)不受contextually typed 或以任何方式受到基类或实现接口的类似属性的影响。当然,有一个 check 属性匹配,但这发生在 正常类型推断发生之后。所以在下面的代码中:

class Foo {
  x = "hello";
}

interface Interface {
  x: "hello" | "goodbye";
}

class Bar implements Interface {
  x = "hello"; // error!
}

class Base {
  x: "hello" | "goodbye" = "hello";
}
class Baz extends Base {
  x = "hello"; // error!
}

属性x 的推断在FooBarBaz 中是相同的。 "hello" 最初被解释为字符串文字,但是当它分配给x 属性时,它是widened to string according to the rule

  • 为具有初始化程序且没有类型注释的 ... 非只读属性推断的类型是初始化程序的扩展文字类型。

这对Foo 很好,但会导致BarBaz 出错,因为stringimplementsextends 约束不匹配。呸!当他们看到这一点时,几乎每个人都同意不应该这样。当然,实现的接口或扩展类应该会影响属性推断的发生方式。

好吧,他们tried and failed 之前修复了这个multiple times;现在它只是没有发生。显然,这些变化最终造成的麻烦比解决的问题还要多。关于所涉及问题的一个很好的评论是here

我希望有一天会有某种更新来缓解这个痛点。现在,您只需要使用类型注释或其他类型提示来让编译器知道该做什么。因此,在BarBaz 示例中,您可以这样做:

class Bar implements Interface {
  x: Interface['x'] = "hello"; // okay, "hello" | "goodbye"
}

Bar 中,我们使用显式类型注释使Bar["x"]Interface["x"] 完全匹配(这意味着您可以稍后将x 更改为"goodbye" 而不是"hello")。这通常适用于您不尝试对属性进行专业化,而只是使其兼容的情况。并且:

class Baz extends Base {
  readonly x = "hello"; // okay, readonly narrows to "hello" only
}

Baz 中,我们使用readonly 来提示编译器使x 尽可能窄,因此x 只能是"hello"。它比Base["x"] 窄。当您将属性视为 discriminant 以区分一个子类型时,这通常是合适的。

希望其中一个适合你。

好的,希望对您有所帮助;祝你好运!

【讨论】:

    【解决方案3】:

    正如我在评论中所说,如果变量是在方法之外定义的,那么 TypeScript 可能不太擅长干扰类型。

    要解决此问题,您只需将声明移到构造函数中即可,如您所见here:

    class Thing extends AbstractThing {
      constructor() {
        super();
        this.commands = [
          {
            plan: 10
          }
        ];
      }
    }
    

    但这可能只是引擎中的一个错误

    【讨论】:

      【解决方案4】:

      在您的接口定义中,您将计划属性的类型限制为值 0、10、20 之一。但是当您在类中分配命令属性时:

      commands = [
        {
          plan: 10
        }
      ]
      

      plan 属性的类型为 number。即使您已将其设置为 10,type 也不受任何限制,因此由于它比接口中 plan 的类型限制更少,因此编译器会拒绝它。

      您可以通过多种方式解决此问题,但最简单的方法是将对象强制转换为接口:

      commands = [
        (<IPlan>{
          plan: 10
        })
      ]
      

      记住类型(或多或少)使用集合逻辑。想象一下,你是类型检查程序员,你看到了文字表达式

      {
        plan: 10,
      }
      

      有几种解释方式。这里仅列出两个用于我们的目的:

      • 有一个对象,它有一个计划,一个属性,唯一允许的值是 10。
      • 有一个对象,它有一个计划、属性,它是一个数字。

      这里还有各种其他考虑因素(null?其他键?)但编译器不够聪明,无法推断出,因为您使用了文字“10”,所以类型与接口兼容。加上值可能会在运行时发生变化,等等。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2020-08-20
        • 2020-06-19
        • 2022-11-20
        • 2019-01-29
        • 2022-12-06
        • 2020-08-07
        • 2021-09-18
        • 2023-02-22
        相关资源
        最近更新 更多