Typescript: ключ typeof союза между объектом и примитивом никогда не бывает - PullRequest
0 голосов
/ 07 сентября 2018

Сначала немного контекста к моему вопросу: у меня есть проект, в котором я получаю объект через Socket.IO, поэтому у меня нет информации о типе. Кроме того, это довольно сложный тип, поэтому необходимо много проверять, чтобы убедиться, что полученные данные хороши.

Проблема в том, что мне нужно получить доступ к свойствам локального объекта, заданного строками в полученном объекте. Это работает нормально для первого измерения, поскольку я могу привести спецификатор свойства типа any к 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) или я что-то здесь упускаю?

1 Ответ

0 голосов
/ 07 сентября 2018

Если у вас есть объединение, доступны только общие свойства. keyof даст вам общедоступные ключи типа.

Для strUnionTyped, когда объединение между string и строковым литералом типа 'hi' результирующий тип будет иметь те же свойства, что и строка, так как два типа в объединении имеют те же ключи, что и строка.

Для objUnionTyped и complexUnionTyped объединение не имеет общих ключей, поэтому результат будет never

Для manuallyComplexUnionTyped вы получаете ключи string, потому что то, что вы написали, на самом деле (keyof string) | { name: string, innerObj: { name: string }} не keyof (string | { name: string, innerObj: { name: string }}), поэтому вы получаете ключи string в объединении с указанным вами типом объекта.

Чтобы получить ключи всех членов союза, вы можете использовать условный тип:

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

Редактировать

Причина того, что условные типы помогают получить ключи всех членов объединения, заключается в том, что условные типы распределяют по параметрам открытого типа. Так что в нашем случае

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