typescript - 仅在特定情况下在 TS 接口中设置密钥
问题描述
我正在尝试为“订单”创建一个界面。“书”的订单是一种特殊情况,它应该有一个必填的“标题”字段。那么如何创建 IBookOrder 并将其与 IOtherOrder 结合以制作通用订单界面?
type productType = "book" | "pen" | "notebook"
interface IOtherOrder {
product: productType, //all productType(s) except book
amount: number,
productCode: number,
description: string
}
interface IBookOrder {
product: productType, //how do I fix this to "book" type
amount: number,
productCode: number,
description: string,
title: string, //required field only for order of "book"
}
//need to combine these here
interface IOrderValues extends IBookOrder, IOtherOrder {}
const penOrder: IOrderValues = {
product: "pen",
amount: 5,
description: "ink pen",
productCode: 123,
title: "This should not be allowed" // <<= incorrect
}
const bookOrder: IOrderValues = {
product: "book",
amount: 15,
description: "Awesome TS book",
productCode: 321,
title: "Typescript 101" // <<= this should be required
}
解决方案
您可以将和的product
属性显式标记为您想要的联合的子集:IOtherOrder
IBookOrder
ProductType
interface IOtherOrder {
product: "pen" | "notebook" // or Exclude<ProductType, "book">
amount: number,
productCode: number,
description: string
}
interface IBookOrder {
product: "book"
amount: number,
productCode: number,
description: string,
title: string,
}
请注意,您根本不需要ProductType
在这些定义中使用,但是如果您认为以后可能会动态地将更多字符串文字类型添加到联合中,那么Exclude<ProductType, "book">
(使用实用程序 type )之Exclude<T, U>
类的东西也会自动添加它们。
当涉及到您的IOrderValues
类型时,您不能将其设为特定(非泛型)interface
类型;接口类型必须具有静态已知的成员,并且条件包含title
是不可能的。您当然不希望它同时扩展and IBookOrder
,IOtherOrder
因为这样IOrderValues
需要 each都是它们两者(现在这是不可能的,因为它们的product
属性是互斥的)。
相反,您可能希望它是IBookOrder
和IOtherOrder
接口的联合;如果你想给它一个名字,你可以通过type
别名来做到这一点:
type IOrderValues = IBookOrder | IOtherOrder;
(请注意,这会使您的I
前缀用词不当;是否需要此类前缀取决于您,并且命名约定可能超出了此问题的范围,因此我不会在这里继续讨论。)
现在您的示例代码的行为符合要求:
const penOrder: IOrderValues = {
product: "pen",
amount: 5,
description: "ink pen",
productCode: 123,
title: "This should not be allowed" // error!
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Object literal may only specify known properties,
// and 'title' does not exist in type 'IOtherOrder'
}
const bookOrder: IOrderValues = {
product: "book",
amount: 15,
description: "Awesome TS book",
productCode: 321,
title: "Typescript 101"
} // okay
请记住,尽管您看到的错误penOrder
依赖于过多的属性检查,只有当您将对象文字直接分配给期望具有较少属性的类型的地方时才会出现这种情况。如果你以不同的方式做事,你最终可能会得到额外的属性:
const obj = {
product: "pen" as const,
amount: 5,
description: "ink pen",
productCode: 123,
title: "My wonderful rainbow ink pen",
numberOfColors: 7
}
const penOrder: IOrderValues = obj; // no error
多余的属性本身并不违反类型系统;TypeScript 具有结构子类型,因此虽然IOtherOrder
要求在声明中具有属性,但不禁止具有额外的属性。事实上,这样的额外属性允许支持接口和类扩展;没有它,接口和类层次结构不会形成类型层次结构,并且事情会更难使用。
我个人认为这很好,你不应该担心多余的属性会偷偷溜进去;您不能真正防止所有可能的多余属性,因此您可能应该确保您的代码不依赖于它们的缺失(例如,如果它们存在,它应该简单地忽略它们;它不应该期望Object.keys()
只返回已知的键......请参阅为什么 Object.keys 在 TypeScript 中不返回 keyof 类型?)。
如果您想明确排除title
出现在 中IOtherOrder
,您可以更改IOtherOrder
声明:
interface IOtherOrder {
product: Exclude<ProductType, "book">
amount: number,
productCode: number,
description: string
title?: never; // <-- add this
}
说这title
是一个可选属性,其值将是不可能的never
类型。因此 的唯一有效值title
是undefined
。不过,这可能有点矫枉过正。
推荐阅读
- c# - 如何使子类覆盖具有部分实现的方法?
- javascript - 数组映射的格式输出
- laravel - 如何在此代码中显示数据库的日期?
- r - 如何在 R 中制作这个箱线图?
- python - 如何使我的 pygame 游戏窗口可调整大小?
- scala - 如何用下划线替换空格并对scala数组/列表中的值进行编码
- python - Tkinter 类和方法
- java - 使用 Spring Boot 的 Post API 返回嵌套 json 的空值
- python - 如何实现更改文件名读取的迭代方式以及如何将结果合并到单个excel文件中
- c - addr2line 如何使用虚拟地址进行内核空间调试?