【问题标题】:How to configure a conditional type based on value of a property?如何根据属性的值配置条件类型?
【发布时间】:2020-05-31 18:14:15
【问题描述】:

我有许多Item 对象和一个处理它们的方法。问题是某些项目可能具有其他项目没有的某些属性。

type ItemType = 'ApiGateway' | 'ApiGatewayEndpoint' | 'ApiGatewayMethod';

export default interface Item {
    ref?: string;
    position: Position;
    type: ItemType;
    children?: Item[];
}

function process(item: Item) {...}

现在,假设我想创建一个单独的接口ApiGatewayMethodItem(扩展Item),它有一个称为“方法”的附加字符串属性,表示它是GET 还是POST 或其他东西。有没有一种方法可以让我输入 process({type: 'ApiGatewayMethod'}) tsc 就会开始抱怨缺少的属性 method ?我知道 TS 对“条件类型”有很好的支持,但我以前没有使用过它们,我很难理解它们......

假设我有接口

interface ApiGatewayMethodItem extends Omit<Item, 'type'> {
    type: 'ApiGatewayMethod';
    method: string;
}

现在,当我调用process 函数时,我需要编译器抱怨method 属性在我不指定时丢失,但指定类型ApiGatewayMethod

【问题讨论】:

  • 您希望所有Items 都拥有method 属性吗?还是只有ApiGatewayMethodItem
  • 没有。只有 ApiGatewayMethodItem 将具有属性 method。然后我将拥有ApiGatewayEndpointItem,它不会拥有method 属性,但会拥有path。我知道我应该在我的流程函数 (typescriptlang.org/docs/handbook/release-notes/…) 上使用代码类型,但文档很难解读

标签: typescript typescript-typings


【解决方案1】:

您可以仅使用不具有“方法”属性的 2 个 ItemType 来定义您的项目类型。然后将属性 'method' 仅添加到类型 'ApiGatewayMethodItem'

type ItemType = 'ApiGateway' | 'ApiGatewayEndpoint';

export default interface DefaultItem {
    ref?: string;
    position: Position;
    type: ItemType;
    children?: DefaultItem[];
}

interface ApiGatewayMethodItem extends Omit<DefaultItem, 'type'> {
    type: 'ApiGatewayMethod';
    method: string;
}

type Item = DefaultItem | ApiGatewayMethodItem;

function process(item: Item) {

}

process ({
    type: 'ApiGatewayMethod',
    position: null
})

【讨论】:

  • 我解释错了吗?每个人都给了我相同的错误解决方案......如果我调用process({type: 'ApiGatewayMethod'}),由于缺少method而不会导致任何编译问题,这就是我想用条件类型得到的(typescriptlang.org/docs/handbook/release-notes/…)跨度>
  • 我编辑了答案以使其更清晰。代码现在给你一个编译错误,因为缺少“方法”
  • 我想我错过了。它似乎工作得很好!谢谢!
  • 没问题!感谢您提出这个问题。对“省略”一无所知 Typescript 有很多定义类型的概念。
【解决方案2】:

您可以将Item 设为联合类型,并定义一个接口来保存所有项目共有的属性:

export default interface CommonItemProperties {
    ref?: string;
    position: Position;
    children?: Item[];
}

interface ApiGatewayMethodItem extends CommonItemProperties {
    type: 'ApiGatewayMethod';
    method: 'GET' | 'POST';
}

interface ApiGatewayEndpointItem extends CommonItemProperties {
  type: 'ApiGatewayEndpoint';
  path: string;
}

type Item = ApiGatewayEndpointItem | ApiGatewayMethodItem;

function process(item: Item) {
  switch (item.type) {
    case ('ApiGatewayMethod'):
      return item.method; // this is not a type error - typescript 'knows' the item is a ApiGatewayMethodItem
    case ('ApiGatewayEndpoint'):
      return item.path;   // this is not a type error - typescript 'knows' the item is a ApiGatewayEndpointItem
  }
}

switch 语句只是一个示例,说明 typescript 如何根据对type 的检查来确定您传入的项目类型。

【讨论】:

  • 有人愿意解释一下否决票吗?如果这不能回答问题,我很高兴知道为什么这样我可以提供进一步的解释
  • 不是我的反对意见,但这不是问题所在。 TS 中的条件类型是有原因的。我希望在缺少属性时得到编译时错误,而不仅仅是让它工作
  • @KamilJanowski 我想也许我们可以使用一些关于你想要什么的额外信息;我们都给了你同样的答案;如果您只想将ApiGatewayMethodItem 传递给process,为什么要键入它以便可以传递Item
【解决方案3】:

您可以使用discrimated unions 解决此问题。这样编译器就可以从type 字段中找出哪些成员可用。

type ItemTypeWithoutMethod = 'ApiGateway' | 'ApiGatewayEndpoint' | 'ApiGatewayMethod';
type ItemTypeWithMethod = "ApiGatewayMethodItem";

interface ItemBase {
    ref?: string;
    position: Position;
    children?: Item[];
}

interface ItemWithoutMethod extends ItemBase {
  type: ItemTypeWithoutMethod
}

interface ItemWithMethod extends ItemBase {
    type: ItemTypeWithMethod;
    method: string;
}

export type Item = ItemWithMethod | ItemWithoutMethod;

function doSomethingWithItem(item: Item) {
  if (item.type == "ApiGatewayMethodItem") {
    console.log(item.method);
  }
}

Playground link

【讨论】:

  • 不能解决问题。 doSomethingWithItem({type: 'ApiGatewayMethod'}) 不会导致编译失败,而这正是我要找的
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-12-01
  • 2020-12-27
  • 2020-06-03
相关资源
最近更新 更多