问题描述
type SomeObject = {
A: boolean
B: boolean
C: boolean
....
}
是否有一种简单/有效的方法来检查所有其他字段(指定字段除外)的 OR
是否为 false
?
我们可以通过手动检查每个字段来暴力破解:
let foo:SomeObject = {
A: false
B: false
C: false
}
let isA = !(foo.B || foo.C)
let isB = !(foo.A || foo.C)
let isC = !(foo.A || foo.B)
但我相信有一种更优雅的方式来实现这一点。
解决方法
如果您只需要编译时检查,使用映射类型和交集(为了“美化”生成的类型而抛出的身份映射类型)很容易做到:
type SomeObject = {
A: boolean
B: boolean
C: boolean
}
type OnlyOneTrue<T extends object,K extends keyof T> = {
[P in K] : true
} & {
[P in Exclude<keyof T,K>]: false
};
type Identity<T> = { [P in keyof T] : T[P] }
//{ A: true; } & { B: false; C: false; }
type test = OnlyOneTrue<SomeObject,"A">;
//{ A: true; B: false; C: false; }
type pretty = Identity<test>;
如果您需要编译时类型保护以便稍后可以缩小类型,那么您需要使用联合。前一种类型可以成为另一种实用类型的垫脚石:
type PossibleOnlyTrue<T extends object> = {
[P in keyof T]: OnlyOneTrue<T,P>
}[keyof T];
//OnlyOneTrue<SomeObject,"A"> | OnlyOneTrue<SomeObject,"B"> | OnlyOneTrue<SomeObject,"C">
type union = PossibleOnlyTrue<SomeObject>;
const process = (obj: union) => {
//type is narrowed to a union member:
if(obj.A) {
console.log(obj); //OnlyOneTrue<SomeObject,"A">
}
};
如果您需要在顶部进行运行时检查,有一些选项。下面是基于之前的辅助映射类型(PossibleOnlyTrue
和 OnlyOneTrue
)的类型和运行时保护:
const runtime = <K extends keyof union>(obj: union,key: K) : obj is Extract<union,{ [P in K] : true }> => {
return Object.entries(obj).every(([k,v]) => k === key || !v );
};
{
const obj:union = { A:true,B:false,C:false };
if(runtime(obj,"A")) obj //OK,obj is OnlyOneTrue<SomeObject,"A">
}