是否可以在 TypeScript 中创建严格的“A 或 B”类型? [复制]

Posted

技术标签:

【中文标题】是否可以在 TypeScript 中创建严格的“A 或 B”类型? [复制]【英文标题】:Is it possible to create strict 'A or B' types in TypeScript? [duplicate] 【发布时间】:2021-10-20 09:57:01 【问题描述】:

我在输入 React 组件时遇到了以前从未遇到过的问题。我已经简化了下面的问题。

interface Circle 
  kind?: "circle";
  radius: number;

 
interface Square 
  kind: "square";
  sideLength: number;

 
type Shape = Circle | Square;

const shape: Shape = 
  sideLength: 7,
  radius: 7,
;

Link to TypeScript Playground containing the above

在上面的示例中,我希望 shape 变量声明会在 TypeScript 中引发错误,因为 Circle 和 Square 都没有 both sideLengthradius。定义的对象既不是圆形也不是方形,所以在我看来它不应该是一个有效的形状。

是否可以定义类型以使以下项目有效或错误(如标签所示)。

// Valid
const shape: Shape = 
  kind: 'circle',
  radius: 7,
;

// Valid
const shape: Shape = 
  radius: 7,
;

// Valid
const shape: Shape = 
  kind: 'square',
  sideLength: 7,
;

// Error
const shape: Shape = 
  sideLength: 7,
  radius: 7,
;

// Error
const shape: Shape = 
  sideLength: 7,
;

// Error
const shape: Shape = 
  kind: 'square',
  radius: 7,
;

// Error
const shape: Shape = 
  kind: 'circle',
  sideLength: 7,
;

编辑:

为了进一步说明,在我的用例中,kind 在 Circle 上是可选的。对于那些熟悉 React 的人来说,我要解决的实际问题是样式组件暴露的 as 属性。我想要一个组件接受一个可选的as prop,它允许用户将组件(默认为button)更改为链接(a)。如果用户指定 as="a",那么我希望 TypeScript 能够在用户尝试在现在的链接上使用特定于按钮的道具(例如,disabled)时进行兼容。 as 属性是可选的,因为我不希望所有实现者都必须通过它。在我上面的简化示例中,as 类似于kind,因此为什么kind 是可选的。

【问题讨论】:

“定义的对象既不是圆形也不是方形,所以在我看来它不应该是一个有效的形状。” 请记住,子类型仍然是它们的有效示例超类型。你的shape 一个有效的形状,因为它是一个有效的Circle,因为它有一个radius: number 属性并且没有kind 属性(所以kind 属性与Circle)。它有一个额外的属性,但这只是使它成为一个子类型,而不是一个无效的Circle。不过,我很感兴趣,对象文字的多余属性检查(在您的 Playground 中启用)在这里被击败。 为什么将kind in circle 定义为可选?你的类型const shape: Shape = radius: 7; 是有效的圈子。为什么?因为它具有所有必需的道具(种类是可选的) 如果在Circle 中设置kind,问题就消失了,因为shape 不再是有效的Shape(它既不是Square 也不是Circle) :playground 形状最好由一个区域来定义。然后方形和圆形就可以实现自己的计算了。我移植了一次go interface example,它与blog post 中的形状有关。 请参阅the answer 其他问题以获取更多信息。如果您愿意,可以使用ExclusifyUnion<T> 将具有已知键的对象类型的联合转换为一个新的联合,其中联合的成员是互斥的。如果我将其用于您的示例,它将变为this code。祝你好运! 【参考方案1】:

请注意,您已将kind 属性定义为可选属性,因此如果您创建的形状对象只有radius 属性仍然有效circle

看看这个对象

const shape: Shape = 
  sideLength: 7,
  radius: 7,
;

这是具有一个额外属性的有效圆圈。

因此,在您所说的某些情况下要引发错误,您应该将 kind 属性设置为必需属性:

interface Circle 
  kind: "circle";
  radius: number;

 
interface Square 
  kind: "square";
  sideLength: number;

 
type Shape = Circle | Square;

const shape2: Shape = 
  radius: 7,//Error
;

PlaygroundLink

【讨论】:

“这是具有一个额外属性的有效圆圈。” 确实,尽管正如我在 cmets 中所说,TypeScript 通常对对象文字的多余属性检查将不允许这样做shape: Circle 而不是 shape: Shape(即使它是有效的 Circle)。 感谢您的回复。我刚刚更新了这个问题以回应这个问题,一些 cmets 详细说明了为什么(在我的用例中)我希望 kind 是可选的。

以上是关于是否可以在 TypeScript 中创建严格的“A 或 B”类型? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

如何在 TypeScript 中创建自定义类型

如何像在 babelrc 中创建依赖别名一样在 typescript 中创建类型别名?

我可以在 TypeScript 中创建一个对每个键值对都是类型安全的键值字典吗?

在 TypeScript 中创建电子菜单?

如何在 JavaScript/TypeScript 中创建具有非唯一键的地图?

如何在 TypeScript 中创建循环引用类型?