【问题标题】:Typescript union types & inferring differing properties without extra type checks打字稿联合类型和推断不同的属性而无需额外的类型检查
【发布时间】:2021-02-04 17:55:01
【问题描述】:

我在联合类型方面有点挣扎,我想知道打字稿是否可以在没有额外检查的情况下推断出值。假设我有这个接口设置(为简洁起见省略了 IBaseDiscount),其中值可以不同,但​​嵌套的折扣类型名称是固定的

interface IFlatDiscount extends IBaseDiscount {
    value:{ formatted: string; value: number };
    discountType: {
        name: DiscountType.flat;
        id: number;
    };
}

interface IOpenDiscount extends IBaseDiscount {
    value?: number;
    discountType: {
        name: DiscountType.open;
        id: number;
    };
}

export interface IPercentageDiscount extends IBaseDiscount {
    value: number;
    discountType: {
        name: DiscountType.percentage;
        id: number;
    };
}

export type IDiscount = IOpenDiscount | IPercentageDiscount | IFlatDiscount;

现在在我的代码中,当我尝试使用这些值时,我最终不得不执行以下操作

if (discount.discountType.name === DiscountType.flat && typeof discount.value === 'object) {
    // now my value is properly typed -- if I leave out the object check it doesnt know the correct type for the value
}

打字稿是否有适当的方法根据 discountType.name 推断值,而不是在任何地方对值进行所有检查?

【问题讨论】:

  • 你想要一个discriminated union。然后你可以在判别式上使用 switch 语句。
  • 我知道它适用于顶级属性——但它可以从这样的嵌套值中推断出来吗?
  • IDK,有什么特殊的原因你不能扁平化接口的结构或添加顶级判别式吗?
  • 我的意思是技术上是的,但这是 API 返回的形状,需要发回,所以我不想更改它。只是看看我是否可以删除额外的值检查
  • 除非我做错了什么,apparently it can't narrow the type

标签: typescript


【解决方案1】:

首先,您在这里进行了不必要的类型检查:

if (discount.discountType.name === DiscountType.flat && 
  typeof discount.value === 'object') {
}

以下内容就足够了:

if (typeof discount.value === 'object') {
  
}

如果你想根据discountType.name来决定接口,typescript不能从嵌套的属性类型检查中推断类型。

基本上有两种方式:

  • 您使用“as”关键字(因为您确定类型正确):
switch(discount.discountType.name) {
  case DiscountType.flat:
    console.log((discount as IFlatDiscount).value.formatted);
    break;
  case DiscountType.open:
    console.log((discount as IOpenDiscount).value);
    break;
  case DiscountType.percentage:
    console.log((discount as IPercentageDiscount).value);
}
  • 更好的是,您可以使用泛型类型:
export enum DiscountType {
    flat,
    open,
    percentage
}


export interface IGenericDiscount<T extends DiscountType> {
    value: T extends DiscountType.flat 
        ? { formatted: string; value: number } 
        : 
            (T extends DiscountType.open 
                ? (number | undefined) 
                : number
            );
    discountType: {
        name: T;
        id: number;
    }
}

const genericDiscount: IGenericDiscount<DiscountType.open> = JSON.parse('{}');

console.log(genericDiscount.value);

Typescript playground

【讨论】:

  • 我将研究您发布的那个通用接口,并在上面循环返回。我知道从技术上讲,我可以只检查对象——但未来可能会添加更多折扣类型,因此仅检查对象可能会在没有人注意到的情况下打破界限
  • @topched 始终牢记 ts 不是运行时类型检查器。类型检查是您的责任。 Typescript 更像是文档。它可以帮助你不犯错误,但不能阻止它。
  • 是的,我知道对于其他开发人员 + 单元测试和辅助函数有一点额外的想法。将这个答案标记为已接受,因为我想做的事情并不可能,但这有助于稍微清理它
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2022-01-05
  • 1970-01-01
  • 2022-01-22
  • 2018-08-12
  • 2021-07-02
  • 2019-05-26
  • 1970-01-01
相关资源
最近更新 更多