在打字稿中创建仅允许两个属性之一的接口?

时间:2019-11-09 18:29:27

标签: typescript

假设我有一个界面

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

我如何强制只允许property1和property2中的一个被允许,但其中一个必须存在?

2 个答案:

答案 0 :(得分:1)

如果要只允许列表中的一个属性,则需要union对象类型,其中每个对象类型都允许一个特定属性,而不允许所有其他属性。 TypeScript并不能完全禁止您使用特定属性,但是您可以做些接近的事情:将其设为值类型为never的可选属性。实际上,这将允许类型为undefined的属性,但是undefined属性和缺少的属性之间并没有太大的区别(和差异isn't captured well in TypeScript anyway)。

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

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中的一个属性,而{{1 }},您可以这样定义它:

U

这使用一堆mappedconditional类型来构建所需的联合。我可以解释它是如何工作的,但是要花很多时间。我以前也做过类似的事情。可以在here中找到类似类型的更详细说明。

无论如何,我们现在可以像这样定义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;

ICart

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

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

好的,希望能有所帮助;祝你好运!

Link to code

答案 1 :(得分:0)

// 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类型存在的问题是为了使更多密钥具有这样的规则而引起的。因此,对于两个从属字段效果很好,但不能使其工作更多。但这也许会对您有所帮助。对我来说,这是一个解决难题的好方法。