我正在尝试使用类型系统对特定模式进行建模,其中对象仅由一组可能属性中的单个属性组成。
换句话说,该类型将是部分类型,但只允许一个属性。
interface PossibleProperties {
cat?: AllPropsOfSameType;
dog?: AllPropsOfSameType;
cow?: AllPropsOfSameType;
}
interface ShouldBeExactlyOneOfPossibleProperties {
[P in keyof PossibleProperties]: AllPropsOfSameType; // Wupz, this allows for 0 or more...
}
Run Code Online (Sandbox Code Playgroud)
我见过需要至少一个属性的解决方案:
type AtLeastOne<T, U = {[K in keyof T]: Pick<T, K> }> = Partial<T> & U[keyof U]
Run Code Online (Sandbox Code Playgroud)
但我需要类似AtMostOne<T, U = {[K in keyof T]: Pick<T, K> }>or 的东西ExactlyOne<T, U = {[K in keyof T]: Pick<T, K> }>,它可能是AtMostOneand的交集类型AtLeastOne
如果这可能的话有什么想法吗?
小智 9
使用整篇文章中的信息,我得到以下解决方案:
type Explode<T> = keyof T extends infer K
? K extends unknown
? { [I in keyof T]: I extends K ? T[I] : never }
: never
: never;
type AtMostOne<T> = Explode<Partial<T>>;
type AtLeastOne<T, U = {[K in keyof T]: Pick<T, K> }> = Partial<T> & U[keyof U]
type ExactlyOne<T> = AtMostOne<T> & AtLeastOne<T>
Run Code Online (Sandbox Code Playgroud)
我的第一个想法是创建一个联合类型,例如:
type ExactlyOne =
{ cat?: AllPropsOfSameType } |
{ dog?: AllPropsOfSameType } |
{ cow?: AllPropsOfSameType };
Run Code Online (Sandbox Code Playgroud)
可以使用分布式条件类型:
type ExactlyOne<T, TKey = keyof T> = TKey extends keyof T ? { [key in TKey]: T[TKey] } : never;
type ShouldBeExactlyOneOfPossibleProperties = ExactlyOne<PossibleProperties>;
Run Code Online (Sandbox Code Playgroud)
但它仍然允许分配具有多个属性的对象:
// this assignment gives no errors
const animal: ShouldBeExactlyOneOfPossibleProperties = {
cat: 'a big cat',
dog: 'a small dog'
};
Run Code Online (Sandbox Code Playgroud)
这是因为 TypeScript 中的联合类型是包容性的,目前您无法创建独占联合类型。看到这个答案。
所以我们需要以某种方式禁止额外的属性。一个选项可能是使用nevertype,但不幸的是,不可能创建 type 的可选属性,never因为never | undefined给出undefined. 如果可以使用undefined其他属性,您可以使用以下可怕的类型:
type ExactlyOne<T, TKey = keyof T> = TKey extends keyof T
? { [key in Exclude<keyof T, TKey>]?: never } & { [key in TKey]: T[key] }
: never;
Run Code Online (Sandbox Code Playgroud)
结果类型如下所示:
({
dog?: undefined;
cow?: undefined;
} & {
cat: string | undefined;
}) | ({
cat?: undefined;
cow?: undefined;
} & {
dog: string | undefined;
}) | ({
cat?: undefined;
dog?: undefined;
} & {
cow: string | undefined;
})
Run Code Online (Sandbox Code Playgroud)
太可怕了……但已经接近预期了。
这种方法的缺点是,如果您尝试分配具有多个属性的对象,则会出现不具描述性的错误消息,例如以下分配:
const animal: ShouldBeExactlyOneOfPossibleProperties = {
cat: 'a big cat',
dog: 'a small dog'
};
Run Code Online (Sandbox Code Playgroud)
给出以下错误:
Type '{ cat: string; dog: string; }' is not assignable to type '({ dog?: undefined; cow?: undefined; } & { cat: string | undefined; }) | ({ cat?: undefined; cow?: undefined; } & { dog: string | undefined; }) | ({ cat?: undefined; dog?: undefined; } & { cow: string | undefined; })'.
Type '{ cat: string; dog: string; }' is not assignable to type '{ cat?: undefined; dog?: undefined; } & { cow: string | undefined; }'.
Type '{ cat: string; dog: string; }' is not assignable to type '{ cat?: undefined; dog?: undefined; }'.
Types of property 'cat' are incompatible.
Type 'string' is not assignable to type 'undefined'.(2322)
Run Code Online (Sandbox Code Playgroud)
另一种方法:您可以模拟此答案中建议的独占联合类型。但在这种情况下,一个额外的属性被添加到对象中。
type ExactlyOne<T, TKey = keyof T> = TKey extends keyof T
? { [key in TKey]: T[TKey] } & { prop: TKey }
: never;
const animal: ExactlyOne<PossibleProperties> = {
prop: 'cat',
cat: 'a big cat'
};
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
8356 次 |
| 最近记录: |