问题描述
在回答问题之前,我将首先列出我的整个思考过程——部分原因是搜索引擎可能会选择某些关键字,而且答案提供者不必进行不必要的阐述。
在 TypeScript 中,U
和 A
两种类型的联合类型 B
看起来像
type U = A | B;
这意味着 U
类型的值可以是 A
或 B
类型。
假设我想定义一个带有两个参数(F
类型的 x
和 X
类型的 y
)返回的箭头函数类型 Y
Z
类型的值。看起来像:
type F = (x: X,y: Y) => Z; // (1)
到目前为止一切顺利。但是,现在我希望 F
类型的函数接受 X1
或 X2
作为其第一个参数的类型,以及 Y1
或 Y2
作为第二个参数的类型。从上面可以看出,直觉会提示
type F = (x: X1|X2,y: Y1|Y2) => Z; // (2)
...但这是错误的! (x: X1,y: Y1) => Z
类型的值不能分配给 F
类型的值,因为上面的 TypeScript 类型 F
代表具有联合类型 X1|X2
的函数第一个参数的类型,联合类型 Y1|Y2
作为第二个参数的类型。事实上,(2)等价于:
type Xs = X1 | X2;
type Ys = Y1 | Y2;
type F = (x: Xs,y: Ys) => Z; // (3),equivalent to (2)
手动,我们的问题是通过写来解决
type F = ( (x: X1,y: Y1) => Z ) | ( (x: X1,y: Y2) => Z ) | ( (x: X2,y: Y1) => Z ) | ( (x: X2,y: Y2) => Z ) // (4)
参数的联合已经被函数联合所取代,以前联合的类型分布在它们上面。如果您愿意,它看起来有点像 TypeScript 类型的笛卡尔积。
这是我的问题:我只知道可接受的参数类型(Xs
和 Ys
)的联合类型,所以我不能手动提前写出这样的笛卡尔积。回到我的第一行代码(U
类型的定义),很明显,如果只给出 U
而没有 A
和 B
,你仍然可以使用它来意味着联合中的任何一种类型都可以。如何指定联合中的任何一种类型都可以用于箭头函数的参数的类型定义?
解决方法
这是对带有两个参数的函数执行此操作的一种方法:我假设您希望结果是标记联合,以便您可以知道在运行时传递给函数的参数类型。
为了描述每个参数的联合类型的标签,它接受像{x1: X1,x2: X2}
这样的对象,这样'x1'
和'x2'
成为X
联合的标签,结果使用了 'x1-y1'
之类的标签。如果您需要保留原始标签,添加 xTag: Xi,yTag: Yi
之类的属性会很简单。
type CreateTaggedUnionOf2Functions<X,Y,T> = {
[Xi in keyof X]: {
[Yi in keyof Y]: {
tag: `${Xi & string}-${Yi & string}`,func: (x: X[Xi],y: Y[Yi]) => T
}
}[keyof Y]
}[keyof X]
// --- Test ---
type Test = CreateTaggedUnionOf2Functions<{x1: X1,x2: X2},{y1: Y1,y2: Y2},Z>
/* equivalent to:
type Test = {tag: "x1-y1",func: (x: X1,y: Y1) => Z}
| {tag: "x1-y2",y: Y2) => Z}
| {tag: "x2-y1",func: (x: X2,y: Y1) => Z}
| {tag: "x2-y2",y: Y2) => Z}
*/
我对编写一个专门且仅适用于具有两个参数的函数的解决方案并不满意,所以这里是一个通用版本:它相当复杂,但比我想象的要简单。也许它可以以某种方式简化。
// --- Implementation ---
type CreateTaggedUnion<A extends any[]>
= A extends [infer X]
? {
[Xi in keyof X]: {tag: Xi,arr: [X[Xi]]}
}[keyof X]
: A extends [infer X,...infer Y]
? {
[Xi in keyof X]: CreateTagMap<Y> extends infer U ? {
[Yi in keyof U]: {
tag: `${Xi & string}-${Yi & string}`,arr: U[Yi] extends any[] ? [X[Xi],...U[Yi]] : never
}
}[keyof U] : never
}[keyof X]
: never
type CreateTagMap<A extends any[]>
= CreateTaggedUnion<A> extends infer U
? [U] extends [{tag: infer K,arr: any}]
? {[Ki in K & string]: (U & {tag: Ki})['arr']}
: never
: never
type CreateTaggedUnionOfFunctions<A extends any[],R>
= CreateTagMap<A> extends infer U
? {
[K in keyof U]: U[K] extends any[] ? {tag: K,func: (...args: U[K]) => R} : never
}[keyof U]
: never
// --- Tests ---
interface X1 {x: 1}
interface X2 {x: 2}
interface Y1 {y: 1}
interface Y2 {y: 2}
interface Z1 {z: 1}
interface Z2 {z: 2}
interface O {o: 3}
type TestM = CreateTagMap<[{x1: X1,{z1: Z1,z2: Z2}]>
/** equivalent to TypeScript type:
* type TestM = {
* "x1-y1-z1": [X1,Y1,Z1];
* "x1-y1-z2": [X1,Z2];
* "x1-y2-z1": [X1,Y2,Z1];
* "x1-y2-z2": [X1,Z2];
* "x2-y1-z1": [X2,Z1];
* "x2-y1-z2": [X2,Z2];
* "x2-y2-z1": [X2,Z1];
* "x2-y2-z2": [X2,Z2];
* }
*/
type TestU = CreateTaggedUnion<[{x1: X1,z2: Z2}]>
/** equivalent to TypeScript type:
* type TestU = {
* tag: "x1-y1-z1";
* arr: [X1,Z1];
* } | {
* tag: "x1-y1-z2";
* arr: [X1,Z2];
* } | {
* tag: "x1-y2-z1";
* arr: [X1,Z1];
* } | {
* tag: "x1-y2-z2";
* arr: [X1,Z2];
* } | {
* tag: "x2-y1-z1";
* arr: [X2,Z1];
* } | ...
*/
type TestF = CreateTaggedUnionOfFunctions<[{x1: X1,z2: Z2}],O>
/** equivalent to TypeScript type:
* type TestF = {
* tag: "x1-y1-z1";
* func: (args_0: X1,args_1: Y1,args_2: Z1) => O;
* } | {
* tag: "x1-y1-z2";
* func: (args_0: X1,args_2: Z2) => O;
* } | {
* tag: "x1-y2-z1";
* func: (args_0: X1,args_1: Y2,args_2: Z1) => O;
* } | {
* tag: "x1-y2-z2";
* func: (args_0: X1,args_2: Z2) => O;
* } | {
* tag: "x2-y1-z1";
* func: (args_0: X2,args_2: Z1) => O;
* } | ...
*/