Jus*_*dei 5 types type-conversion typescript typescript-generics
我有这样的类型:
enum Type {
A = 'A',
B = 'B',
C = 'C'
}
type Union =
| {
type: Type.A | Type.B;
key1: string
}
| {
type: Type.C;
key2: string
}
type EnumToUnionMap = {
[T in Type]: {
[k in keyof Extract<Union, {type: T}>]: string
}
}
Run Code Online (Sandbox Code Playgroud)
我遇到的问题typeof EnumToUnionMap[Type.A]是never(实际上,它是一个通用的键签名,[x: string]: string但那是因为当是或时Extract<Union, {type: T}>返回类型)而是neverTType.AType.Btypeof EnumToUnionMap[Type.C]
{
type: Type.C,
key2: string
}
Run Code Online (Sandbox Code Playgroud)
正如预期的那样。
这一切都是有道理的,因为typeinEnumToUnionMap[Type.A]是Type.A | Type.B,Type.A != (Type.A | Type.B)所以它们不匹配,我们得到never.
基本上我需要做这样的事情:
type EnumToUnionMap = {
[T in Type]: {
[k in keyof Extract<Union, T in Union['type']>]: string
}
}
Run Code Online (Sandbox Code Playgroud)
为什么我需要这样做:
我收到来自具有以下形状的警报端点的响应:
{
type: Type,
key1: string,
key2: string
}
Run Code Online (Sandbox Code Playgroud)
Type.A和Type.B提供的警报key1同时Type.C提供key2。
我需要将响应中的键映射到网格中的列名(其中某些警报类型共享一组通用键,但列的显示名称不同):
const columnMap: EnumToUnionMap = {
[Type.A]: {
key1: 'Column name'
// Note that in actuality this object will contain
// multiple keys (i.e. multiple columns) for a
// given `Type` so creating a map
// between `Type -> column name` is not possible.
},
[Type.B]: {
key1: 'Different column name'
},
[Type.C]: {
key2: 'Another column name'
}
}
Run Code Online (Sandbox Code Playgroud)
这样,我可以执行以下操作:
const toColumnText = (alert) => columnMap[alert.type]
...
if (alert.type === Type.A) {
const key1ColumnName = toColumnText(alert).key1 // typed as string
const key2ColumnName = toColumnText(alert).key2 // Typescript warns of undefined key
}
Run Code Online (Sandbox Code Playgroud)
正如您所注意到的,您实际上不能在这里使用实用Extract程序类型,因为联合成员Union与您拥有的候选类型之间的关系{type: T}不是简单的可分配性。相反,您想要找到满足以下条件的U联合成员。您可能不得不忘记 TypeScript 提供的实用程序类型,而是自己执行类型操作。UnionT extends U["type"]
一种可能的定义EnumToUnionMap是:
type EnumToUnionMap = { [T in Type]: Union extends infer U ? U extends { type: any } ? (
T extends U["type"] ? { [K in keyof U as Exclude<K, "type">]: U[K] } : never
) : never : never }
Run Code Online (Sandbox Code Playgroud)
这可能看起来有点令人畏惧;让我们确保它至少能满足您的要求。IntelliSense 显示它的计算结果为:
/* type EnumToUnionMap = {
A: { key1: string; };
B: { key1: string; };
C: { key2: string; };
} */
Run Code Online (Sandbox Code Playgroud)
看起来不错。
现在我们知道它可以满足您的要求,那么它是如何做到的呢?让我们将定义分成几部分并分析每一部分:
type EnumToUnionMap = { [T in Type]: Union 扩展推断 U ? U 扩展 { 类型:任意 } ?(
T 扩展 U[“类型”] ?{ [K in keyof U as Exclude]: U[K] } : 从不
):从不:从不}
正如在您的版本中一样,我们正在映射枚举值Type.A、Type.B和Type.C。对于每个这样的枚举值T,我们需要分成Union它的联合成员,并收集我们关心的成员。写入Union extends infer U ? U extends { type: any } ? ...使用条件类型推断复制Union到新的类型参数中U,然后可以使用该参数创建分布式条件类型,其中U表示 的各个联合成员Union。因此,从现在开始,每当我们看到时U,我们所处理的只是事物的片段Union,而不是整个事物。
type EnumToUnionMap = { [T in Type]: Union 扩展推断 U ? U 扩展 { 类型:任意 } ?(
T extends U["type"] ? { [K in keyof U as Exclude]: U[K] } : 从不
):从不:从不}
为了确定选择U联合体的哪个联合体成员,我们评估条件类型。当且仅当的属性是 的超类型时,该检查为 true 。如果是且是,则这是真的。但如果is且is ,则这是错误的。通过在检查为假时返回,我们将只处理 的“正确”成员。UnionT extends U["type"] ? ... : neverT extends U["type"]UtypeTTType.AU{type: Type.A | Type.B, ...}TType.AU{type: Type.C, ...}neverUnion
type EnumToUnionMap = { [T in Type]: Union 扩展推断 U ? U 扩展 { 类型:任意 } ?(
T 扩展 U[“类型”] ?{ [K in keyof U as Exclude]: U[K] } : 从不
):从不:从不}
所以我们找到了正确的 union member U,但我们不想只是返回它,因为它仍然包含不需要的type属性。我们可以Omit<U, "type">使用实用程序类型返回Omit,但不幸的是,这会导致冗长的 IntelliSense,Omit<{type: Type.A | Type.B; key1: string}, "type">而不是理想的结果{key1: string}。因此,在这里我使用映射类型中的键重新映射Omit来编写自己的类型(文档解释了这种替代方法的工作原理)。Omit
就这样吧;该EnumToUnionMap类型遍历 的每个成员Type,提取Union其type属性是其超类型的成员,并忽略type该成员的属性。
| 归档时间: |
|
| 查看次数: |
368 次 |
| 最近记录: |