【问题标题】:Create an interface in typescript in which only one of two properties are allowed?在打字稿中创建一个界面,其中只允许两个属性之一?
【发布时间】:2022-04-19 03:18:10
【问题描述】:

假设我有一个界面

interface ICart {
    property1?: string,
    propert2?: string,
    someOtherProperty: string
}

如何强制只允许 property1 和 property2 之一,但其中一个需要存在?

【问题讨论】:

  • 将其合并为两种类型:typescriptlang.org/docs/handbook/…
  • {property1: string, property2?: never, someOtherProperty: string} | {property1?: never, property2: string, someOtherProperty: string} 应该可以工作
  • @jcalz 在界面很大的情况下是否有简洁的方法来做到这一点
  • 喜欢this?
  • @jcalz 谢谢,我认为这可能是我可以使用的东西,但我仍然是打字稿的初学者,所以可能需要更多阅读才能理解它。

标签: typescript


【解决方案1】:

如果您想只允许列表中的一个属性,您需要一个对象类型的union,其中每个对象类型都允许一个特定的属性而不允许所有其他属性。 TypeScript 并不完全允许您禁止特定属性,但您可以做一些接近的事情:将其设置为值类型为 never 的可选属性。在实践中,这将允许undefined 类型的属性,但undefined 属性和缺失属性之间并没有太大区别(并且在使用普通编译器选项see ms/TS#13195 的TypeScript 中没有很好地捕捉到差异)。

因此,对于上面的示例,您想要的类型如下所示:

type ICartManual = {
    property1: string;
    property2?: undefined;
    someOtherProperty: string;
} | {
    property1?: undefined;
    property2: string;
    someOtherProperty: string;
}

您可以验证它的行为是否符合您的要求:

const i1: ICartManual = {
    property1: "prop1",
    someOtherProperty: "other"
}

const i2: ICartManual = {
    property2: "prop2",
    someOtherProperty: "other"
}

const iBoth: ICartManual = { // error!
//    ~~~~~ <-- property1 is incompatible with undefined
    property1: "prop1",
    property2: "prop2",
    someOtherProperty: "other"
}

const iNeither: ICartManual = { // error!
//    ~~~~~~~~ <-- property2 is missing
    someOtherProperty: "other"
}

如果你有一个很大的接口,并且想要使用两种对象类型 TU 并创建一个新的对象类型,它只需要 T 中的一个属性和 U 中的所有属性,你可以像这样定义它这个:

type OneKeyFrom<T, M = {}, K extends keyof T = keyof T> = K extends any ?
    (M & Pick<Required<T>, K> & Partial<Record<Exclude<keyof T, K>, never>>) extends infer O ?
    { [P in keyof O]: O[P] } : never : never;

这使用了一堆 mappedconditional 类型来构建你想要的联合。我可以解释它是如何工作的,但这需要很多话。我以前做过类似的事情;查看here 以获得对类似类型的更深入描述。

无论如何,我们现在可以这样定义ICart

type ICart = OneKeyFrom<{ property1: string, property2: string }, { someOtherProperty: string }>;

并且您可以验证(例如通过 IntelliSense)它与手动编写的类型相同(除了写入属性的顺序,它不会更改类型):

/* type ICart = {
    property1: string;
    property2?: undefined;
    someOtherProperty: string;
} | {
    property2: string;
    property1?: undefined;
    someOtherProperty: string;
} */

Link to code

【讨论】:

    【解决方案2】:
    // utility type which blocks two properties of the object coexisting 
    type NeverTogether<A extends object, Key1 extends keyof A, Key2 extends keyof A extends Key1 ? never : keyof A> = 
      Omit<A, Key1 | Key2> & (({
        [k in Key1]: A[Key1]
      } & {[k in Key2]?: never}) | ({
        [k in Key1]?: never
      } & {[k in Key2]: A[Key2]}))
    
    interface ICart {
        property1: string,
        property2: string,
        someOtherProperty: string
    }
    
    type IC = NeverTogether<ICart, 'property1', 'property2'>;
    
    // error never together
    const a: IC = {
      property1: '1',
      property2: '2',
      someOtherProperty: '2'
    }
    
    // error one needs to be there
    const b: IC = {
      someOtherProperty: '2'
    }
    
    // correct
    const c: IC = {
      property2: '2',
      someOtherProperty: '2'
    }
    
    // correct
    const d: IC = {
      property1: '1',
      someOtherProperty: '2'
    }
    

    NeverTogether 类型存在的问题是组合它以便为更多键设置这样的规则。所以对两个依赖字段很有效,但不能让它更适用。但也许这会对你有所帮助。对我来说,这是一个很好的谜题。

    【讨论】:

      猜你喜欢
      • 2016-10-07
      • 2019-12-22
      • 2017-03-23
      • 2022-07-27
      • 1970-01-01
      • 2021-09-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多