打字稿:对象和基元之间的 keyof typeof union 永远不会

Typescript: keyof typeof union between object and primitive is always never

首先,我的问题有一些上下文:我有一个项目,我在其中通过 Socket.IO 接收一个对象,因此我没有关于它的类型信息。此外,它是一种相当复杂的类型,因此需要进行大量检查以确保接收到的数据是正确的。

问题是我需要访问由接收到的对象中的字符串指定的本地对象的属性。这对于第一个维度工作正常,因为我可以将任何类型的 属性 说明符转换为 keyof typeof 我想要访问的任何内容(例如 this.property[<keyof typeof this.property> data.property])。

结果变量的类型显然是一个相当冗长的联合类型(联合了this.property拥有的所有属性的所有类型)。一旦这些属性之一是非原始类型 keyof typeof subproperty 就被推断为 never.

通过之前的检查,我可以保证 属性 存在并且我 99% 确定代码一旦编译就会 运行。只是编译器在抱怨。

下面是一些非常简单的代码,可以重现此行为以及观察到的和预期的类型。

const str = 'hi';
const obj = {};
const complexObj = {
    name: 'complexObject',
    innerObj: {
        name: 'InnerObject',
    },
};

let strUnion: typeof str | string;              // type: string
let objUnion: typeof obj | string;              // type: string | {}
let complexUnion: typeof complexObj | string;   // type: string | { ... as expected ... }

let strTyped: keyof typeof str;                 // type: number | "toString" | "charAt" | ...
let objTyped: keyof typeof obj;                 // type: never (which makes sense as there are no keys)
let complexObjTyped: keyof typeof complexObj;   // type: "name" | "innerObject"

let strUnionTyped: keyof typeof strUnion;       // type: number | "toString" | ...
let objUnionTyped: keyof typeof objUnion;       // type: never (expected: number | "toString" | ... (same as string))
let complexUnionTyped: keyof typeof complexUnion;   // type: never (expected: "name" | "innerObject" | number | "toString" | ... and all the rest of the string properties ...)
let manuallyComplexUnionTyped: keyof string | { name: string, innerObj: { name: string }};  // type: number | "toString" | ... (works as expected)

这是 TypeScript(版本 3)的已知限制还是我在这里遗漏了什么?

如果您有联合,则只能访问公共属性。 keyof 将为您提供某种类型的可公开访问的密钥。

对于 strUnionTyped string 和字符串文字类型 'hi' 之间的联合,结果类型将具有与字符串相同的属性,因为联合中的两种类型具有相同的属性键作为字符串。

对于 objUnionTypedcomplexUnionTyped 联合没有公共键,所以结果将是 never

对于manuallyComplexUnionTyped你得到的是string的key,因为你写的其实是(keyof string) | { name: string, innerObj: { name: string }}而不是keyof (string | { name: string, innerObj: { name: string }})所以你得到的是string的key在与您指定的对象类型的联合中。

要获取联合所有成员的键,您可以使用条件类型:

type AllUnionMemberKeys<T> = T extends any ? keyof T : never;
let objUnionTyped: AllUnionMemberKeys<typeof objUnion>;
let complexUnionTyped: AllUnionMemberKeys<typeof complexUnion>;

编辑

条件类型有助于获取所有联合成员的键的原因是因为条件类型 distribute 优于裸类型参数。所以在我们的例子中

AllUnionMemberKeys<typeof objUnion> = (keyof typeof obj) | (keyof string)