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

Bir*_*sky 3 types functional-programming typescript typescript2.0

我希望TypeScript在映射到这样的联合时强制执行穷举:

type Union = 
  { type: 'A', a: string } |
  { type: 'B', b: number }
Run Code Online (Sandbox Code Playgroud)

Union事件处理程序:

const handle = (u: Union): string =>
  theMap[u.type](u);
Run Code Online (Sandbox Code Playgroud)

如果在这里我们能以某种方式从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
};
Run Code Online (Sandbox Code Playgroud)

jca*_*alz 7

给定theMap定义的类型,很难(或者可能不可能)哄骗TypeScript,为您提供一种方法来表达详尽性检查(Extract<U, X>对于每个组合类型的联合只包含一个处理程序)和健全性约束(每个处理程序U都是为联盟的特定组成类型).

但是,可以X根据更一般的类型进行定义,您也可以从中表达上述约束.让我们先看一下更通用的类型:

type Union = { type: "A"; a: string } | { type: "B"; b: number };

const theMap: {
  [K in Union["type"]]: (u: Extract<Union, { type: K }>) => string
} = {
  A: ({ a }) => "this is a: " + a,
  B: ({ b }) => "this is b: " + b
};
Run Code Online (Sandbox Code Playgroud)

这里theMap(u.type)(u)是从theMap(u.type)原始属性u到组成类型的映射,从中theMap(u.type)删除.从此,u相当于Union.

让我们在类型映射上定义一些操作,如theMap:

const handle = (u: Union): string =>
  (theMap[u.type] as (v: Union) => string)(u); // need this assertion
Run Code Online (Sandbox Code Playgroud)

您可以验证这theMap相当于Union:

const handle = (u: Union): string =>
  u.type === "A" ? theMap[u.type](u) : theMap[u.type](u); // redundant
Run Code Online (Sandbox Code Playgroud)

此外,定义BaseTypes:

type BaseTypes = {
  A: { a: string };
  B: { b: number };
}
Run Code Online (Sandbox Code Playgroud)

这需要一个关键type并产生联合的组成部分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];
Run Code Online (Sandbox Code Playgroud)

它是一个映射类型,包含每种类型的一个属性Union,它是从特定类型到a 的函数DiscriminatedUnion<BaseTypes>.这是详尽的:如果你离开了一个NarrowedFromUnionK,或把type功能上的NarrowedFromUnion<'A'>属性,编译器会抱怨.

这意味着我们能够省略上明确标注NarrowedFromUnion<'B'>Union,因为类型theMap现在执行此约束.这很好,因为代码中的显式注释不是很安全; 你可能已经切换了注释而没有被编译器警告,因为它只知道输入是一个Union.(这种不合理的类型缩小函数参数称为双变量,它在TypeScript中是混合的祝福.)

现在我们应该string在传入AB参数中使用泛型,因此编译器将检查我们是否正在使用它做一些安全的事情:

type Union = DiscriminatedUnion<BaseTypes>
Run Code Online (Sandbox Code Playgroud)

如果你留下旧签名(B),它会抱怨,除非你用类型断言使它静音.


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