在 TypeScript 中创建一个接口,其中仅允许两个属性中的一个?
假设我有一个接口
interface ICart {
property1?: string,
propert2?: string,
someOtherProperty: string
}
我如何确保只允许 property1 和 property2 中的一个存在,但必须至少有一个存在?
答案#1
如果你想从列表中只允许一个属性,你需要创建一个对象类型的联合类型,每个对象类型允许某个特定属性并禁止其他所有属性。TypeScript 并不能完全禁止某个特定属性,但你可以做一些接近的事情:将其设为可选属性,其值类型为 never。实际上,这将允许该属性的类型为 undefined,但 undefined 属性和缺失属性之间的差别并不大(而且在使用普通编译器选项时,这种差别在 TypeScript 中并没有很好地体现,参见 ms/TS#13195)。
所以,对于你上面的例子,你想要的类型看起来像这样:
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"
}
如果你有一个大型接口,并且想要取两个对象类型T和U,并创建一个新类型,该类型要求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;
这使用了大量的映射类型和条件类型来构建你想要的联合类型。我可以解释它是如何工作的,但需要很多文字。我以前做过类似的事情;可以在这里查看对类似类型的更深入描述。
无论如何,我们现在可以这样定义 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;
} */
代码链接
答案#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 类型的问题在于组合它以便对更多的键应用这样的规则。因此,它对两个相关字段效果很好,但对更多字段无法生效。但也许这会对你有帮助。对我来说,这是一个很有意思的谜题去解决。
