Typescript 如何为区分的联合类型数组编写typeguard?

Typescript 如何为区分的联合类型数组编写typeguard?,typescript,Typescript,我想做的事情的最小代码复制: 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 = DataMa

我想做的事情的最小代码复制:

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中有一个错误:“string | number”类型的参数不能分配给“never”类型的参数

我该如何为这种情况写typeguard

编辑:


如果现在除了断言之外没有解决方案,那么也许有更好的方法来构造这样的数据?

我试图重写它,因为块类型似乎是以错误的方式定义的;我最后得到的是:

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

const data: Array<Block> = []
现在,您可以在最后一行找到问题的根源,这会引发错误:

泛型类型块需要1个类型参数。ts2314

只有当类型具有将属性类型和数据逻辑地联系在一起的泛型参数时,它才有意义。因此,一旦您尝试使其成为一个数组,这就变得不可能了


您可能必须重新构造对象,或者忽略类型错误。

我试图重写它,因为块类型的定义似乎是错误的;我最后得到的是:

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

const data: Array<Block> = []
现在,您可以在最后一行找到问题的根源,这会引发错误:

泛型类型块需要1个类型参数。ts2314

只有当类型具有将属性类型和数据逻辑地联系在一起的泛型参数时,它才有意义。因此,一旦您尝试使其成为一个数组,这就变得不可能了


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

这是我一直称之为相关记录类型的另一个实例,TypeScript编译器无法很好地解释这一点。有关详细信息,请参见,但问题是编译器看到block.type和block.data都是类型的并集,但它不理解只有一些可能性可以同时发生,而其他可能不会。如果您引导编译器遍历这些案例,就可以了:

data.forEach(block => {
    if (block.type in handlers) {
        if (block.type === "foo") {
            handlers[block.type](block.data)
        } else {
            handlers[block.type](block.data)
        }
    }
})
因为在每一种情况下,它都可以将障碍缩小到一个受歧视工会的一个成员。但它不能从总体上对此进行推理。您可能只需要使用类型断言,然后继续,比如:

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

这是我一直称之为相关记录类型的另一个实例,TypeScript编译器无法很好地解释它。有关详细信息,请参见,但问题是编译器看到block.type和block.data都是类型的并集,但它不理解只有一些可能性可以同时发生,而其他可能不会。如果您引导编译器遍历这些案例,就可以了:

data.forEach(block => {
    if (block.type in handlers) {
        if (block.type === "foo") {
            handlers[block.type](block.data)
        } else {
            handlers[block.type](block.data)
        }
    }
})
因为在每一种情况下,它都可以将障碍缩小到一个受歧视工会的一个成员。但它不能从总体上对此进行推理。您可能只需要使用类型断言,然后继续,比如:

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

在原始示例中,基本上是块的副本,基本上是块的副本,是TypeScript中非常常见的数据结构。为什么这是一个错误的方法,如何使它通用解决任何问题?请注意,即使在您的版本中,您也可以有data:Array,它可以恢复受歧视的并集。是的,我也不太明白。您的示例中的块将生成与我的版本基本相同的联合。@jcalz:我知道这是一个有区别的联合,我一直在使用它们。它的定义方法是错误的,因为DataMap只用于提取其属性的类型,而属性不是在映射本身中定义的,而是在另一个对象中定义的,因此您最好完全跳过映射。泛型是必需的,因为数据依赖于类型。当然,您可以枚举所有可能的值,但这会占用数组进行迭代的大部分便利。@Danila:而且它还会丢失一般处理每个块所需的所有信息。我的意思是,如何生成这个并集、使用map或使用自己的方式都无关紧要,或手动硬编码所有选项。最终的结果是一样的,问题是如何使用这种结构来避免任何错误。不过,感谢您的示例,这样写可能会更容易。原始示例中的块是一种非常常见的TypeScript数据结构。为什么这是一个错误的方法,如何使它通用解决任何问题?请注意,即使在您的版本中,您也可以有data:Array,它可以恢复受歧视的并集。是的,我也不太明白。您的示例中的块将生成与我的版本基本相同的联合。@jcalz:我知道这是一个有区别的联合,我一直在使用它们。它的定义方法是错误的,因为DataMap只用于提取其属性的类型,而属性不是在映射本身中定义的,而是在另一个对象中定义的,因此您最好完全跳过映射。泛型是必需的,因为数据依赖于类型。当然,您可以枚举所有可能的值
但这就剥夺了使用数组进行迭代的大部分便利。@Danila:而且它也会丢失您通常处理每个块所需的所有信息。我的意思是,无论您如何生成这个并集,使用map,或使用自己的方式,或手动硬编码所有选项。最终的结果是一样的,问题是如何使用这种结构来避免任何错误。不过,感谢您的示例,这样写可能更容易。