【问题标题】:Is it possible to create strict 'A or B' types in TypeScript? [duplicate]是否可以在 TypeScript 中创建严格的“A 或 B”类型? [复制]
【发布时间】: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。祝你好运!

标签: javascript typescript


【解决方案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 是可选的。
猜你喜欢
  • 1970-01-01
  • 2016-05-04
  • 1970-01-01
  • 2022-01-14
  • 1970-01-01
  • 1970-01-01
  • 2016-09-22
  • 2017-04-23
  • 1970-01-01
相关资源
最近更新 更多