【问题标题】:Union Type missing properties联合类型缺少属性
【发布时间】:2018-08-27 07:51:28
【问题描述】:

假设我有以下类型:

type MessageType = 'example1' | 'example2' | 'example3'

type MessageHead = {
  +type: MessageType
}

type BaseBody = {
  +payload?: any,
  +data?: any
}

type LabelledBody = {
  +labelName: string
}

type MessageBody = BaseBody | LabelledBody

type Message = MessageHead & MessageBody

然后我会像这样消费一条消息:

[{name: 'example1'}, {name: 'potato'}].find(thing => thing.name === message.labelName)

导致以下流异常:

Cannot get message.labelName because:
 • all branches are incompatible:
    • Either property labelName is missing in MessageHead [1].
    • Or property labelName is missing in BaseBody [2].
 • ... 1 more error.

type Message = MessageHead & MessageBody 显示为违规类型

我不明白为什么我的联合类型不允许带有标签名称的消息?

编辑:Tryflow 链接:Tryflow link

【问题讨论】:

  • 我们在几周前遇到了类似的问题并最终放弃了,改用更简单的模式,我们没有尝试“扩展”类型,而是在必要时使用带有选项的单一类型.不太好,但工作得很好。

标签: javascript types flowtype


【解决方案1】:

您的联合类型是否允许带有标签名称的Message。问题是它还允许Message 没有标签名称。考虑一下你在这条线上的工会:

type MessageBody = BaseBody | LabelledBody

这意味着MessageBody 可以是这样的:

type BaseBody = {
  +payload?: any,
  +data?: any
}

type LabelledBody = {
  +labelName: string
}

更进一步,我们可以执行 MessageBody 和 MessageHead 之间的交集,看到 Message 的形状可以是这两种情况之一:

(案例一)

{
  +type: MessageType,  // From MessageHead
  +payload?: any,      // From BaseBody
  +data?: any          // From BaseBody
}

(案例 2)

{
  +type: MessageType,  // From MessageHead
  +labelName: string   // From LabelledBody
}

因此,当 Flow 看到您正在访问 message 对象的 labelName 时,它​​(正确地)相信 message 对象可能看起来像案例 1(上图)。如果我们是情况 1,那么您将无法访问 labelName,因为它不存在,因此会引发错误。解决此问题的最简单方法是创建两种类型的消息,一种具有labelName,另一种具有payloaddata 属性。然后您可以将您的函数注释为接收其中一种类型:

(Try)

type MessageWithLabel = MessageHead & LabelledBody

const exFunc = (message: MessageWithLabel) => {
  [{name: 'example1'}, {name: 'potato'}].find(thing => thing.name === message.labelName)
}

或者,您可以使用disjoint union 告诉流程您正在使用哪种情况。这个策略包括设置一个属性(例如type),它告诉流我们正在处理哪种类型的对象。

(Try)

type MessageWithBody = {|
  +type: 'base',
  +payload?: any,
  +data?: any
|}

type MessageWithLabel = {|
  +type: 'labelled',
  +labelName: string
|}

type Message = MessageWithBody | MessageWithLabel

const exFunc = (message: Message) => {
  if (message.type === 'labelled') {
    const labelName = message.labelName
    return [{name: 'example1'}, {name: 'potato'}].find(thing => thing.name === labelName)

    // Sidenote: 
    // I had to extract labelName to a constant for Flow to typecheck this correctly.
    // So if we used thing.name === message.labelName Flow will throw an Error.
    // If you don't extract it right away, flow thinks the Message may have changed
    // after pretty much any non-trivial action. Obviously it doesn't change in this
    // example, but, hey, Flow isn't perfect.
    // Reference: https://flow.org/en/docs/lang/refinements/#toc-refinement-invalidations
  } else {
     // Do something for the 'base' type of message
  }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-09-09
    • 2019-09-11
    • 1970-01-01
    • 1970-01-01
    • 2019-06-21
    • 2017-02-15
    • 2015-12-02
    • 2019-02-07
    相关资源
    最近更新 更多