为什么Flow拒绝由工会交叉组成的元组?

时间:2017-02-04 11:45:53

标签: javascript flowtype

在下面的代码中,我不理解为什么Flow会拒绝我的kv元组。

// @flow

type String = {
  type: "string",
  value: string
};

type Number = {
  type: "number",
  value: number
};

type Value = Number | String;

type Foo = { foo: "bar" } & Value;

let obj: Foo = {
  foo: "bar",
  type: "number",
  value: 42
};

let kv: [string, Foo] = ["obj", obj];

Flow似乎成功检查objFoo,但不希望我在[string, Foo]元组中使用它。

确实我收到以下错误:

23: let kv: [string, Foo] = ["obj", obj];
                                    ^ intersection type. This type is incompatible with
15: type Foo = { foo: "bar" } & Value;
                                ^ union: Number | String

发生了什么事?

1 个答案:

答案 0 :(得分:1)

很遗憾"尝试流程"并没有为您提供完整的类型错误跟踪。如果你去here并点击" json"你可以深入了解并了解更多关于发生了什么的事情。

问题:非决定论

想到这一点的方法是继承问题。 Flow试图确定Foo是什么类型。因为在Foo的声明中没有任何内容可以表明它是{foo}&数字或{foo}& String,Flow似乎天真地试图同时应用它们。

所以在第23行,流程说,"类型不是Foo因为我希望属性"类型"成为"字符串"和"价值"输入" number"。但如果你换了它,Flow仍然会出错。

这是我想到这个问题的另一种方式:

class String
class Number
class Value extends String || Number  // <= unable to determine what Foo is extending
type Foo = { stuff } & Value // <== ...locked-in non-deterministic

在这种情况下,Flow无法确定&#34; Foo&#34;正在扩展,但如果某个变量被赋值为Value类型,则有一些逻辑来解析该类型。但...

当创建Foo别名时,Value仍然是非确定性的,它就像Flow试图锁定Foo一样。一旦访问属性,Flow就会抛出错误。

幸运的是,有两种简单的方法可以解决这个问题。

脱节联盟

第一个涉及一个名为disjoint unions的功能。 Here's a working example

type Foo = { foo: "bar" }

type String = Foo & {
  type: "string",
  value: string
};

type Number = Foo & {
  type: "number",
  value: number
};

type Value = Number | String;

let obj: Foo = {
  foo: "bar",
  type: "number",
  value: 42
};

let kv: [string, Foo] = ["obj", obj]; // no errors!

为什么这样做?再次,假设您使用继承模式编写了这个:

class Foo;
class String extends Foo; // <== deterministic
class Number extends Foo; // <== deterministic
var Value = String | Number // <== enum of certain classes, so still deterministic

对象类型点差(未发布)

如果您获取Flow的最新主分支,那么还有一个很酷的功能,对象类型的传播。 Here's an example

// @flow (master branch)

type String = {
  type: "string",
  value: string
};

type Number = {
  type: "number",
  value: number
};

type Value = Number | String;

type Foo = { foo: "bar", ...Value }

let obj: Foo = {
  foo: "bar",
  type: "number",
  value: 42
}; // no error!

let otherObj: Foo = {
  foo: "bar",
  type: 'string',
  value: 'I am a string',
}; // no error!