Playground link. 我有一个有区别的对象类型联合:

export type ADiscriminatedUnion = {
    type: 'a',
    dataKeyA: number,
    anotherDataKeyA: number,
} | {
    type: 'b',
    dataKeyB: number,
    anotherDataKeyB: number,
} | {
    type: 'c',
}

type AllTypes = ADiscriminatedUnion['type'];

我创建一个对象,将some种类型(但不是全部)映射到该类型对象中存在的部分属性列表:

const DiscriminatedUnionTypeToListOfDataKeysMap = {
  a: ['dataKeyA'],
  b: ['dataKeyB', 'anotherDataKeyB'],
} as const satisfies {
  [Type in AllTypes]?: Array<
    keyof Omit<Extract<ADiscriminatedUnion, { type: Type }>, 'type'>
  >;
};

type TypesWhichAreMappedToDataKeys =
  keyof typeof DiscriminatedUnionTypeToListOfDataKeysMap;

现在我想创建一个函数,该函数只能接受在第二个代码块的映射中定义的类型对象,并通过列出的属性进行映射.

function doSomethingWithDataKeys(
  data: Extract<ADiscriminatedUnion, { type: TypesWhichAreMappedToDataKeys }>
) {
    const stringifiedDataKeys = DiscriminatedUnionTypeToListOfDataKeysMap[data.type]
        .map(key => `${data[key]}`).join('_');
}

这个错误在data[key]:

Property 'dataKeyA' does not exist on type '{ type: "a"; dataKeyA: number; anotherDataKeyA: number; } | { type: "b"; dataKeyB: number; anotherDataKeyB: number; }'.

TypScript似乎不会缩小DiscriminatedUnionTypeToListOfDataKeysMap[data.type]的范围来将结果值(属性名称数组)与传递的对象的属性进行匹配.我如何指示它这样做?

推荐答案

您遇到了microsoft/TypeScript#30581的变体.当dataunion type时,TypScript不理解data.typedata[key]之间的相关性.它需要"立即"这样做,它不会预先将data缩小到ADiscriminatedUnion中的每个unions 成员,并分别判断每个缩小.对于在一个地方发生的两名成员unions 来说,这是可以的,但这种事情不会扩大规模.

让TypScript立即分析一般内容的方法是使用generics.对于相关联合,有一种在microsoft/TypeScript#47109中描述的特定类型的重构有效.该方法是用某个"基本"key-Value类型、或用该类型的mapped types、或用这些类型的通用indexes into来表示您正在做的一切.

对于您的示例,最干净的重构如下.首先,让我们定义基本key-Value类型:

interface BaseType {
  a: {
    dataKeyA: number;
    anotherDataKeyA: number;
  };
  b: {
    dataKeyB: number;
    anotherDataKeyB: number;
  };
  c: {
    dataKeyC: number;
    anotherDataKeyC: number;
  };
}

然后您的discriminated union可以在BaseType中成为通用,特别是,将成为ms/TS#47109中创造的distributive object type:

type ADiscriminatedUnion<K extends keyof BaseType = keyof BaseType> =
  { [P in K]: { type: P } & BaseType[P] }[K]

没有类型参数的名为ADiscriminatedUnion的类型(使用keyof BaseType中的default type argument)相当于您的版本,但它的表示方式是为了让编译器更容易遵循其余代码:

export const DiscriminatedUnionTypeToListOfDataKeysMap = {
  a: ['dataKeyA'],
  b: ['dataKeyB', 'anotherDataKeyB'],
} as const satisfies { [K in keyof BaseType]?: (keyof BaseType[K])[] }

type TypesWhichAreMappedToDataKeys = 
  keyof typeof DiscriminatedUnionTypeToListOfDataKeysMap;

function doSomethingWithDataKeys<K extends TypesWhichAreMappedToDataKeys>(
  data: ADiscriminatedUnion<K>
) {
  const d: { [K in TypesWhichAreMappedToDataKeys]: (keyof BaseType[K])[] } =
    DiscriminatedUnionTypeToListOfDataKeysMap;
  const stringifiedDataKeys = d[data.type]
    .map(key => `${data[key]}`).join('_');
}

我必须将DiscriminatedUnionTypeToListOfDataKeysMap扩展到类型{ [K in TypesWhichAreMappedToDataKeys]: (keyof BaseType[K])[] },这让编译器理解DiscriminatedUnionTypeToListOfDataKeysMap[data.type]属于类型(keyof BaseType[K])[],这意味着key属于类型keyof BaseType[K],这有效,因为data可赋给BaseType[K],所以data[key]BaseType[K][keyof BaseType[K]],这是所有BaseType[K]值类型的联合.由于所有这些都是可序列化的number,因此代码会进行编译.


请注意,您could从原来的ADiscriminatedUnion类型开始,然后使用它到compute BaseType,但这更复杂:

type BaseType = { [T in ADiscriminatedUnion as T['type']]: 
  { [K in keyof T as K extends "type" ? never : K]: T[K] } 
};

然后您需要使用分发对象类型版本ADiscriminatedUnion而不是原始版本,以便TypScript遵循相关性:

type DiscU<K extends keyof BaseType = keyof BaseType> = 
  { [P in K]: { type: P } & BaseType[P] }[K]

只有当你无法控制原来的ADiscriminatedUnion类型时,我才会走这条路.但无论哪种方式,它都有效.

Playground link to code

Typescript相关问答推荐

如果存在一处房产,请确保存在其他房产

为什么TypScript对条件函数类型和仅具有条件返回类型的相同函数类型的处理方式不同?

在OpenLayers 9.1中使用内置方法时,TypScript缩小联合类型

有没有一种方法可以在保持类型的可选状态的同时更改类型?

泛型函数即使没有提供类型

如何提取密钥及其对应的属性类型,以供在新类型中使用?

类型脚本`Return;`vs`Return unfined;`

使用泛型keyof索引类型以在if-condition内类型推断

错误TS2403:后续变量声明必须具有相同的类型.变量';CRYPTO';的类型必须是';CRYPATO';,但这里的类型是';CRYPATO';

在保留类型的同时进行深层键名转换

按函数名的类型安全类型脚本方法包装帮助器

判断映射类型内的键值的条件类型

打字错误推断

为什么&Quot;元素隐式具有';Any';类型,因为...即使我已经打字了,也不能作为索引显示错误吗?

从非文字数组(object.keys和array.map)派生联合类型

对于VUE3,错误XX不存在于从不存在的类型上意味着什么?

类型';只读<;部分<;字符串|记录<;字符串、字符串|未定义&>';上不存在TS 2339错误属性.属性&q;x&q;不存在字符串

在TS泛型和记录类型映射方面有问题

React-跨组件共享功能的最佳实践

如何在Vuetify 3中导入TypeScript类型?