typescript - 类型化对象联合的详尽映射

标签 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
};

最佳答案

TS2.8+ 更新

自从 conditional types 发布以来,强类型 theMap 所需的操作变得容易多了。在这里,我们将使用 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
};

super 简单!不幸的是,自 TS2.7 左右以来,编译器不再允许您调用 theMap(u.type)(u)。函数 theMap(u.type) 与值 u 相关,但编译器看不到。相反,它将 theMap(u.type)u 视为独立的联合类型,并且不允许您在没有类型断言的情况下调用另一个:

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

或者无需手动遍历可能的联合值:

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

我一直建议人们为此使用断言。

我有一个关于此类 correlated types 的未解决问题,但我不知道是否会支持它。无论如何,再次祝你好运!


TS2.7及以下答案:

鉴于定义的类型 Union,很难(或者可能不可能)哄骗 TypeScript 为您提供一种方法来表达详尽性检查( theMap 只包含联合的每个组成类型的一个处理程序)和健全性约束( theMap 中的每个处理程序都用于联合的特定组成类型。

但是,可以根据更通用的类型来定义 Union,您也可以从中表达上述约束。让我们先看看更通用的类型:

type BaseTypes = {
  A: { a: string };
  B: { b: number };
}

在这里,BaseTypes 是从原始 typeUnion 属性到从中删除 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 的类型了:

const theMap: {[K in Union['type']]: (u: NarrowedFromUnion<K>) => string } = {
  A: ({ a }) => 'this is a: ' + a,
  B: ({ b }) => 'this is b: ' + b
};

它是一个 mapped type,包含 Union 中每种类型的一个属性,它是从该特定类型string 的函数。这是详尽无遗的:如果您遗漏 AB 之一,或者将 B 函数放在 A 属性上,编译器会报错。

这意味着我们可以省略 {a}{b} 上的显式注释,因为 theMap 的类型现在强制执行此约束。这很好,因为代码中的显式注解并不安全;您可以切换注释而不被编译器警告,因为它只知道输入是 Union 。 (这种函数参数的不合理类型缩小称为 bivariance,它在 TypeScript 中喜忧参半。)

现在我们应该让handle在传入的type参数的Union中泛型:

TS2.7+ 更新,由于缺乏对我一直调用的 correlated types 的支持,以下函数需要类型断言。

const handle = <K extends Union['type']>(u: NarrowedFromUnion<K>): string =>
  (theMap[u.type] as (_: typeof u) => string)(u);

好吧,就这么多了。希望能帮助到你。祝你好运!

关于typescript - 类型化对象联合的详尽映射,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46641380/

相关文章:

javascript - R.head 的类型怎么可能是 'chain a'

具有适当子条目的 Angular 库

c++ - boost::serialization Archive::register_type 如何工作?

条件类型中的 TypeScript 类型推断

functional-programming - SKI 变换,如何用函数式语言编程

haskell - "error"函数的存在如何影响 Haskell 的纯度?

angular - 导入 lettable operators 和 observable 创建方法

javascript - ngx-tinymce-editor没有导出成员 'NgxTinymceModule'

javascript - TypeScript 上的通用属性以及可选属性的子集

types - 在其他模块中使用 Julia 抽象类型