假设我有一个对象:
type Obj = { a: string, b: string, c: string }
对于Partial<T>
, TS给出了对象属性的所有可能组合,包括对象的完整形式。在我的例子中,我想排除完整的对象(如果可能的话,也排除空的对象)。
所以我想要:
{} // ❌
{ a: string } // ✅
{ b: string } // ✅
{ c: string } // ✅
{ a: string, b: string } // ✅
{ a: string, c: string } // ✅
{ a: string, b: string, c: string } // ❌
我该怎么做呢?
要实现这种行为,您需要为Obj
类型创建两个约束。第一个应该排除"满";键入和第二个- "empty"类型。
第一个约束意味着至少有一个属性应该是undefined
类型的(或者根本不存在)。因此,我们需要一个类型的联合,其中至少有一个属性符合约束。
首先,我们必须映射初始类型以获得省略一个属性的所有可能的类型组合(注意?
修饰符—它确保允许可变数量的属性),并提取与keyof T
的联合:
{
[ P in keyof T ] : {
[ K in Exclude<keyof T, P> ] ?: T[P]
}
}[keyof T]
如果我们不这样做,我们仍然可以指定所有属性,所以我们需要显式地告诉编译器不允许第三个属性。?
修饰符确保我们可以省略属性:
type NotAll<T> = {
[ P in keyof T ] : {
[ K in Exclude<keyof T, P> ] ?: T[P]
} & { [ M in P ] ?: never }
}[keyof T]
第二个约束意味着至少应该定义一个属性。逻辑几乎是相同的,但这次对于每个属性,我们都有一个类型的所有属性,除了一个可选的集合,与一个类型相交,该属性是必需的:
type AtLeastOne<T> = {
[ P in keyof T ] : {
[ K in Exclude<keyof T, P> ] ?: T[P]
} & { [ M in P ] : T[M] }
}[keyof T];
最后,我们需要将第二个约束与第一个约束结合起来。由于第一个约束给出了允许类型的联合,因此AtLeastOne
应该应用于联合的每个成员:
type NotAll<T> = {
[ P in keyof T ] : AtLeastOne<{
[ K in Exclude<keyof T, P> ] : T[P] //note the modifier is moved to `AtLeastOne`
}> & { [ M in P ] ?: never }
}[keyof T];
就这样,让我们来测试我们的类型:
type test = NotAll<Obj>;
const empty : test = {}; //error
const a : test = { a: "test" }; //OK
const ab : test = { a: "test", b: "test" }; //OK
const bc : test = { b: "test", c: "test" }; //OK
const abc : test = { a: "test", b : "test", c: "test" }; //error
游乐场