【问题标题】:How to write typeguard for array of discriminated union types?如何为可区分的联合类型数组编写类型保护?
【发布时间】:2020-10-21 15:14:13
【问题描述】:

我想做的最少的代码复制:

const handlers = {
  foo: (s: string) => s.length,
  bar: (n: number) => n.toFixed(2)
}

type DataMap = {
  [P in keyof typeof handlers]: {
    type: P,
    data: Parameters<typeof handlers[P]>[0]
  }
}

type Block = DataMap[keyof DataMap];

const data: Array<Block> = []

data.forEach(block => {
  if (block.type in handlers) {
    // Error: Argument of type 'string | number' is not assignable to parameter of type 'never'.
    handlers[block.type](block.data)
  }
})

现在 forEach Error: Argument of type 'string | number' is not assignable to parameter of type 'never'. 里面有一个错误

我应该如何为这种情况编写 typeguard?

编辑:

如果现在除了断言之外没有其他解决方案,那么也许有更好的方法来构建此类数据?

【问题讨论】:

标签: typescript


【解决方案1】:

我试图重写它,因为Block 类型似乎被错误地定义了;我最终得到的是:

interface Block<P extends keyof typeof handlers>
{
    type: P,
    data: Parameters<typeof handlers[P]>[0]
}

const data: Array<Block> = []

现在你在最后一行引发了错误:

通用类型 Block&lt;P&gt; 需要 1 个类型参数。 ts(2314)

只有当它有一个将属性typedata 逻辑联系在一起的通用参数时,该类型才有意义。因此,一旦您尝试将其设为数组,这将变得不可能。

您可能必须重组对象或忽略类型错误。

【讨论】:

  • Block 在原始示例中是 discriminated union,这是 TypeScript 中非常常见的数据结构。为什么它会是“错误的方式”,以及如何使其通用解决任何问题?请注意,即使在您的版本中,您也可以拥有data: Array&lt;Block&lt;"foo"&gt; | Block&lt;"bar"&gt;&gt;,这会恢复已区分的联合。
  • 是的,我也不太明白。 Block&lt;keyof typeof handlers&gt; 在您的示例中将生成与我的版本基本相同的联合。
  • @jcalz:我知道这是一个有区别的联盟,我一直在使用它们。它的定义是错误的,因为DataMap 仅用于提取其属性的类型,并且属性不是在地图本身而是在另一个对象中定义的,因此您不妨完全跳过地图。泛型是必需的,因为data 依赖于type。当然,您可以枚举所有可能的值,但这会带走数组进行迭代的大部分便利。
  • @Danila:它还会丢失一般处理每个块所需的所有信息。
  • 我的意思是,不管你如何生成这个联合,用地图,用你的方式,或者手动硬编码所有选项。最终结果是相同的,问题是如何使用这种结构来避免any。谢谢你的例子,这样写可能更容易。
【解决方案2】:

这是我一直称之为“相关记录类型”的另一个实例,TypeScript 编译器无法很好地推理。有关详细信息,请参阅microsoft/TypeScript#30581,但问题是编译器看到block.typeblock.data 都是类型的联合,但它不明白只有一些可能性可以同时发生而其他可能性不是。如果你让编译器遍历这些案例,那很好:

data.forEach(block => {
    if (block.type in handlers) {
        if (block.type === "foo") {
            handlers[block.type](block.data)
        } else {
            handlers[block.type](block.data)
        }
    }
})

因为在每种情况下,它都可以将block 缩小为受歧视工会的成员之一。但它不能一般地推理它。您可能应该只使用类型断言并继续前进,例如:

data.forEach(block => {
    (handlers[block.type] as (args: Block['data']) => any)(block.data)
})

Playground link to code

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-01-14
    • 2020-02-16
    • 2017-05-18
    • 2021-02-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-02-27
    相关资源
    最近更新 更多