Typescript 从Union类型中提取,其中鉴别器也是Union

Typescript 从Union类型中提取,其中鉴别器也是Union,typescript,types,type-conversion,typescript-generics,Typescript,Types,Type Conversion,Typescript Generics,我有这样一种类型: 枚举类型{ A=‘A’, B=‘B’, C='C' } 类型联合= | { 类型:A型| B型; 键1:字符串 } | { 类型:C型; 键2:字符串 } 类型EnumToUnionMap={ [T型]:{ [k in keyof Extract]:字符串 } } 我遇到的问题是typeofenumtounionmap[Type.A]是never(实际上,它是一个通用的密钥签名,如[x:string]:string,但这是因为Extract在T为Type.A或Type.B时

我有这样一种类型:

枚举类型{
A=‘A’,
B=‘B’,
C='C'
}
类型联合=
| {
类型:A型| B型;
键1:字符串
}
| {
类型:C型;
键2:字符串
}
类型EnumToUnionMap={
[T型]:{
[k in keyof Extract]:字符串
}
}
我遇到的问题是
typeofenumtounionmap[Type.A]
never
(实际上,它是一个通用的密钥签名,如
[x:string]:string
,但这是因为
Extract
T
Type.A
Type.B
时返回Type
类型
[Type.C]

{
类型:C型,
键2:字符串
}
正如所料

这一切都是有意义的,因为
EnumToUnionMap[type.A]
中的
type
type.A | type.B
type.A!=(type.A | type.B)
,所以它们不匹配,我们得到
永不

基本上,我需要这样做:

类型EnumToUnionMap={
[T型]:{
[k in keyof Extract]:字符串
}
}
我为什么需要这样做:

我从具有以下形状的警报端点接收响应:

{
 type: Type,
 key1: string,
 key2: string
}
Type.A
Type.B
的警报都提供
key1
,而
Type.C
的警报提供
key2
我需要将响应中的键映射到网格中的列名(某些警报类型共享一组公共键,但列的显示名称不同):

通过这种方式,我可以执行以下操作:

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
}

TypeScript具有使用
&
缩小类型的自然能力,因此我想尝试一种不需要太多技巧的解决方案。下面是实现预期结果的完整代码。请注意
toColumnText
函数的键入,这也是解决方案的一部分

enum Type {
  A = 'A',
  B = 'B',
  C = 'C'
}

type Union =
 | {
     type: Type.A | Type.B;
     key1: string;
   }
 | {
     type: Type.C;
     key2: string
   }

type FindByType<TWhere, T extends Type> = TWhere extends { type: infer InferredT }
  ? (InferredT extends T ? (TWhere & { type: T }) : never)
  : never;

type EnumToUnionMap = {
  [T in Type]: {
    // Change `FindByType<Union, T>[k]` to `string` to force all extracted properties to be strings intead of their original type
    [k in Exclude<keyof FindByType<Union, T>, 'type'>]: FindByType<Union, T>[k];
  }
};

const columnMap: EnumToUnionMap = {
  [Type.A]: { key1: 'Column name' },
  [Type.B]: { key1: 'Different column name' },
  [Type.C]: { key2: 'Another column name' }
}

const toColumnText = <T extends Type>(alert: { type: T }): EnumToUnionMap[T] => columnMap[alert.type];

const alertObj: Union = { type: Type.A, key1: 'test' };
if (alertObj.type === Type.A) {
  const key1ColumnName = toColumnText(alertObj).key1 // typed as string
  const key2ColumnName = toColumnText(alertObj).key2 // Typescript warns of undefined key
}
枚举类型{
A=‘A’,
B=‘B’,
C='C'
}
类型联合=
| {
类型:A型| B型;
键1:字符串;
}
| {
类型:C型;
键2:字符串
}
类型FindByType=TWhere扩展{type:infer-InferredT}
?(推断扩展T?(TWhere&{type:T}):从不)
:从不;
类型EnumToUnionMap={
[T型]:{
//将'FindByType[k]`更改为'string',以强制所有提取的属性都是原始类型的字符串
[k in Exclude]:FindByType[k];
}
};
常量列映射:EnumToUnionMap={
[Type.A]:{key1:'列名'},
[Type.B]:{key1:'不同的列名'},
[Type.C]:{key2:'另一个列名'}
}
常量toColumnText=(警报:{type:T}):EnumToUnionMap[T]=>columnMap[alert.type];
const alertObj:Union={type:type.A,key1:'test'};
如果(alertObj.type===type.A){
const key1ColumnName=toColumnText(alertObj)。key1//键入为字符串
const key2ColumnName=toColumnText(alertObj)。key2//Typescript警告未定义的键
}

正如您所注意到的,您不能在这里真正使用,因为union
union
的成员与您拥有的候选类型
{type:t}
之间的关系不是简单的可分配性。相反,您希望找到union
union
的成员
U
,以便
t扩展U[“type”]
。您可能必须忘记TypeScript提供的实用程序类型,而是自己执行类型操作


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 }
看起来不错


既然我们知道它做了你想要的,它是如何做到的?让我们把定义分成几部分,然后分析每一部分:

type EnumToUnionMap = { [T in Type]: Union extends infer U ? U extends { type: any } ? ( T extends U["type"] ? { [K in keyof U as Exclude]: U[K] } : never ) : never : never } 为了确定要选择的联合
union
的哪个联合成员
U
,我们计算条件类型
T扩展U[“type”]?…:never
。检查
T扩展U[“type”]
为真当且仅当
U
type
属性是
T
的超类型。如果
T
type.a
U
{type:type.a | type.B,…}
,则这是真的。但如果
T
type.a
U
{type:type.C,…}
,则为false。当检查为false时,通过返回
从不
,我们只处理
联合
的“正确”成员

type EnumToUnionMap = { [T in Type]: Union extends infer U ? U extends { type: any } ? ( T extends U["type"] ? { [K in keyof U as Exclude]: U[K] } : never ) : never : never } 类型EnumToUnionMap={[T in type]:联合扩展推断U?U扩展{type:any}( T扩展U[“type”]?{[K在keyof U中作为Exclude]:U[K]}:从不 ):从不:从不} 因此,我们找到了正确的联合成员
U
,但我们不想只返回它,因为它仍然包含不需要的
type
属性。我们可以使用返回
ommit
,但不幸的是,这会导致类似
ommit
的冗长智能感知,而不是理想的
{key1:string}
。因此,我在这里使用编写自己的
ommit
类型(文档说明了
ommit
的替代方法是如何工作的)


接下来;
EnumToUnionMap
type遍历
type
的每个成员,提取
Union
的成员,该成员的
type
属性是其超类型,并从该成员中省略
type
属性


处理响应并使其成为统一的形状,这样您就不需要每隔一行检查一次,这不是更容易吗?@NadiaChibrikova由于应用程序的性质,“处理请求”基本上就是我现在正在做的。我无法真正统一响应而不丢失正确渲染网格所需的详细信息。您能否提供有关
columnMap
变量的更多详细信息?您能否更改此变量的形状?如果
columnMap
Type
变为mapo新的列名使一切变得更容易。@GBF_Gabriel我假设你指的是
columnMap的地图
type EnumToUnionMap = { [T in Type]: Union extends infer U ? U extends { type: any } ? (
    T extends U["type"] ? { [K in keyof U as Exclude]: U[K] } : never
) : never : never }
type EnumToUnionMap = { [T in Type]: Union extends infer U ? U extends { type: any } ? (
    T extends U["type"] ? { [K in keyof U as Exclude]: U[K] } : never
) : never : never }
type EnumToUnionMap = { [T in Type]: Union extends infer U ? U extends { type: any } ? (
    T extends U["type"] ? { [K in keyof U as Exclude]: U[K] } : never
) : never : never }