您要寻找的类型是这样的:
type XArgs =
{ foo: any; fooId?: never; bar: any; barId?: never; } |
{ foo: any; fooId?: never; barId: any; bar?: never; } |
{ fooId: any; foo?: never; bar: any; barId?: never; } |
{ fooId: any; foo?: never; barId: any; bar?: never; };
function x({ foo, fooId, bar, barId }: XArgs) { }
x({ foo: "", bar: "" }); // okay
x({ fooId: "", bar: "" }); // okay
x({ foo: "", fooId: "", bar: "" }); // error
x({ bar: "" }); // error
So XArgs
is a union https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#union-types有四种可能性。我们看第一个:
{ foo: any; fooId?: never; bar: any; barId?: never }
So foo
and bar
是类型的属性any
,所以它们必须存在。但fooId
and barId
are 可选属性 https://www.typescriptlang.org/docs/handbook/2/objects.html#optional-properties(表示为?
) of 不可能的事never type https://www.typescriptlang.org/docs/handbook/2/narrowing.html#the-never-type。没有以下值never
类型,所以你实际上不能提供一个定义的fooId
or barId
那里的属性...并且由于可以省略可选属性,因此 type 的可选属性never
本质上是被禁止的。所以这个类型意味着foo
and bar
是必需的,并且fooId
and barId
被禁止。
其他三个工会成员类似,只是接受和禁止的属性不同。四个工会成员一起XArgs
描述允许的参数的全部范围x()
.
这就是所问问题的答案。
但是手动写出必要的联合可能会非常乏味,特别是如果你的联合中有两个以上的成员的话。排他性工会 https://en.wikipedia.org/wiki/Exclusive_or(您希望只出现一个元素),或者您关心的两组以上属性。
如果是这样,你可以让编译器计算XArgs
如下:
type AllKeys<T> = T extends unknown ? keyof T : never
type ExclusiveUnion<T, K extends PropertyKey = AllKeys<T>> =
T extends unknown ? (T & { [P in Exclude<K, keyof T>]?: never }) : never;
The AllKeys<T>
类型是一个分配条件类型 https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#distributive-conditional-types计算并集的钥匙 https://www.typescriptlang.org/docs/handbook/2/keyof-types.html#the-keyof-type-operator每个工会成员T
, so AllKeys<{a: 0} | {b: 1}>
is "a" | "b"
.
Then ExclusiveUnion<T>
type 是另一种分布式条件类型,它采用像这样的联合{a: string} | {b: number} | {c: boolean}
并产生一个排他版本,其中每个成员明确禁止仅出现在其他成员中的成员。 (它用AllKeys
获取其他成员的钥匙。)这相当于{a: string, b?: never, c?: never} | {a?: never, b: number, c?: never} | {a?: never, b?: never, c: boolean}
.
注意我说的是“等同”;你实际上得到了工会交叉点 https://www.typescriptlang.org/docs/handbook/2/objects.html#intersection-types, like {a: string} & {b?: never, c?: never}
,这可能会变得笨拙。
所以我有一个Expand<T>
递归条件类型 https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-1.html#recursive-conditional-types在展开任何其他别名属性时折叠交集:
type Expand<T> = T extends object ? { [K in keyof T]: Expand<T[K]> } : T;
然后我们定义XArgs
作为一个交集ExclusiveUnion
s, and Expand
让它变得漂亮:
type XArgs = Expand<
ExclusiveUnion<{ foo: any } | { fooId: any }> &
ExclusiveUnion<{ bar: any } | { barId: any }>
>;
这正是
type XArgs =
{ foo: any; fooId?: never; bar: any; barId?: never; } |
{ foo: any; fooId?: never; barId: any; bar?: never; } |
{ fooId: any; foo?: never; bar: any; barId?: never; } |
{ fooId: any; foo?: never; barId: any; bar?: never; };
让我们尝试一下更难手写的类型:
type YArgs = Expand<
ExclusiveUnion<{ a: 0 } | { b: 1 } | { c: 2 }> &
ExclusiveUnion<{ x: 9 } | { y: 8 } | { z: 7 }>
>
/* type YArgs =
{ a: 0, b?: never, c?: never, x: 9, y?: never, z?: never; } |
{ a: 0, b?: never, c?: never, y: 8, x?: never, z?: never; } |
{ a: 0, b?: never, c?: never, z: 7, x?: never, y?: never; } |
{ b: 1, a?: never, c?: never, x: 9, y?: never, z?: never; } |
{ b: 1, a?: never, c?: never, y: 8, x?: never, z?: never; } |
{ b: 1, a?: never, c?: never, z: 7, x?: never, y?: never; } |
{ c: 2, a?: never, b?: never, x: 9, y?: never, z?: never; } |
{ c: 2, a?: never, b?: never, y: 8, x?: never, z?: never; } |
{ c: 2, a?: never, b?: never, z: 7, x?: never, y?: never; } */
看起来不错!