详尽的地图在类型对象的联合

时间:2017-10-09 07:57:03

标签: typescript types functional-programming typescript2.0

我希望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
};

1 个答案:

答案 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的函数。这是详尽无遗的:如果您遗漏stringA之一,或将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); ),除非您使用类型断言将其静音,否则它会发出抱怨。

好的,那是很多。希望能帮助到你。祝你好运!