我希望TypeScript在映射这样的联合时强制执行穷举:
type Union =
{ type: 'A', a: string } |
{ type: 'B', b: number }
Union事件处理程序:
const handle = (u: Union): string =>
theMap[u.type](u);
如果我们能以某种方式从TypeScript获得详尽的检查,那就太棒了:
const theMap: { [a: string]: (u: Union) => string } = {
A: ({a}: { type: 'A', a: string }) => 'this is a: ' + a,
B: ({b}: { type: 'B', b: number }) => 'this is b: ' + b
};
答案 0 :(得分:6)
如果定义了类型Union
,那么很难(或者也许不可能)哄骗TypeScript,为您提供表达穷举性检查的方法(theMap
只包含每个组成类型的一个处理程序union)和健全性约束(theMap
中的每个处理程序用于联合的特定组成类型)。
但是,可以根据更一般的类型定义Union
,您也可以从中表达上述约束。让我们先看一下更通用的类型:
type BaseTypes = {
A: { a: string };
B: { b: number };
}
此处,BaseTypes
是从原始type
的{{1}}属性到已删除Union
的成分类型的映射。由此,type
相当于Union
。
让我们在类型地图上定义一些操作,例如({type: 'A'} & BaseTypes['A']) | ({type: 'B'} & BaseTypes['B'])
:
BaseTypes
您可以验证type DiscriminatedType<M, K extends keyof M> = { type: K } & M[K];
type DiscriminatedTypes<M> = {[K in keyof M]: DiscriminatedType<M, K>};
type DiscriminatedUnion<M, V=DiscriminatedTypes<M>> = V[keyof V];
是否等同于Union
:
DiscriminatedUnion<BaseTypes>
此外,定义type Union = DiscriminatedUnion<BaseTypes>
:
NarrowedFromUnion
取一个键type NarrowedFromUnion<K extends Union['type']> = DiscriminatedType<BaseTypes, K>
并生成与K
的联合的组成部分。因此,type
是联盟的一条腿,NarrowedFromUnion<'A'>
是另一条腿,它们共同组成NarrowedFromUnion<'B'>
。
现在我们可以定义Union
的类型:
theMap
这是一个mapped type,其中包含const theMap: {[K in Union['type']]: (u: NarrowedFromUnion<K>) => string } = {
A: ({ a }) => 'this is a: ' + a,
B: ({ b }) => 'this is b: ' + b
};
中每种类型的一个属性,该属性是特定类型到Union
的函数。这是详尽无遗的:如果您遗漏string
或A
之一,或将B
函数放在B
属性上,编译器会抱怨。
这意味着我们可以在A
和{a}
上省略显式注释,因为{b}
的类型现在正在强制执行此约束。这很好,因为代码中的显式注释不是很安全;您可以切换注释并且没有被编译器警告,因为它只知道输入是theMap
。 (这种不合理的类型缩小函数参数称为bivariance,它在TypeScript中是混合的祝福。)
现在我们应该使Union
在传入的handle
参数的type
中是通用的,因此编译器会检查我们是否正在使用它做一些安全的事情:
Union
如果您保留旧签名(const handle = <K extends Union['type']>(u: NarrowedFromUnion<K>): string =>
theMap[u.type](u);
),除非您使用类型断言将其静音,否则它会发出抱怨。
好的,那是很多。希望能帮助到你。祝你好运!