TypeScript“数字”不可分配给0 | 10 |即使数字为0,也为20

时间:2019-06-07 15:27:38

标签: javascript typescript

如果您在下面的the typescript playground上尝试使用我的代码段,则会在第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; }[]

以下是代码段:

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
    }
  ]
}

4 个答案:

答案 0 :(得分:2)

不确定为什么,但是使用抽象类会使它认为=iferror(query(A:B,"select B where A =TRUE"),"No results found") 的定义是plan,而不是您列出的特定值。

这两个“变通办法”将有助于确保使用正确的类型

number

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

答案 1 :(得分:1)

正如我的评论中所述,如果变量是从方法之外定义的,TypeScript不太可能干扰类型。

要解决此问题,您应该只在构造函数内部移动声明,如您所见here

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

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

答案 2 :(得分:1)

这是一个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!
}

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

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

这对Foo很好,但是会导致BarBaz错误,因为stringimplements或{{1}不匹配}约束。 !几乎每个人都同意,当他们看到这不应该是这种方式时。当然,已实现的接口或扩展类应影响属性推断的方式。

好吧,他们tried and failed之前曾对此multiple times进行了修复;现在它还没有发生。显然,这些更改最终导致的麻烦超过了解决的麻烦。 here是有关所涉及问题的很好的评论。

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

Baz

class Bar implements Interface { x: Interface['x'] = "hello"; // okay, "hello" | "goodbye" } 中,我们使用了显式类型注释使BarBar["x"]完全匹配(这意味着以后可以将Interface["x"]更改为{{1} }代替x)。当您不打算专门化属性,而只是使其兼容时,这通常是合适的。并且:

"goodbye"

"hello"中,我们使用class Baz extends Base { readonly x = "hello"; // okay, readonly narrows to "hello" only } 来提示编译器将Baz保持尽可能的窄,因此readonly只能是x 。它比x窄。当您将该属性视为discriminant来区分一个子类型和另一个子类型时,通常这是适当的。

希望其中一项能为您服务。

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

答案 3 :(得分:0)

在接口定义中,将plan属性的类型约束为值0、10、20之一。但是,当您在类中分配commands属性时:

commands = [
  {
    plan: 10
  }
]

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

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

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

记住类型(或多或少)使用集合逻辑。假设您是类型检查器程序员,并且看到了文字表达式

{
  plan: 10,
}

有几种解释方法。这只是出于我们的目的的两个:

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

这里还有各种各样的其他注意事项(空吗?其他键?),但是编译器还不够聪明,无法推断出这是因为您使用了文字'10',该类型与接口兼容。另外,该值可能会在运行时等发生变化。