Как обратиться к сопоставленному типу с обобщенным индексом c в Typescript? - PullRequest
0 голосов
/ 24 апреля 2020

Я пытаюсь получить доступ к свойству из сопоставленного типа, используя обобщенный тип c. Но я получаю ошибку в комментарии ниже. Какой это правильный способ напечатать что-то вроде этого?

type ThingType = 'typeA' | 'typeB';

interface Thing<T extends ThingType> {
    type: T,
    name: string
};

type Container<T extends ThingType> = {
    [id: string]: Thing<T>
}

type Store<T extends ThingType = ThingType> = {
    [K in T]?: Container<K>
};

const myStore: Store = {
    'typeA': {
        'one': {type: 'typeA', name: 'one'},
        'two': {type: 'typeA', name: 'two'}
    }
};

// This one does not fail
const typeAContainer: Container<'typeA'> | undefined = myStore['typeA'];

function storeThing<T extends ThingType>(thing: Thing<T>) {
    // Error here:
    const container: Container<T> | undefined = myStore[thing.type];
    // Type 'Store<ThingType>[T]' is not assignable to type 'Container<T> | undefined'.
    //  Type 'Container<"typeA"> | Container<"typeB"> | undefined' is not assignable to type 'Container<T> | undefined'.
    //    Type 'Container<"typeA">' is not assignable to type 'Container<T>'.
    //      Type '"typeA"' is not assignable to type 'T'.
    //        '"typeA"' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'ThingType'.
    //          Type 'Store<ThingType>[T]' is not assignable to type 'Container<T>'.
    //            Type 'Container<"typeA"> | Container<"typeB"> | undefined' is not assignable to type 'Container<T>'.
    //              Type 'undefined' is not assignable to type 'Container<T>'.

    // ... code here ...
}

storeThing({type: 'typeA', name: 'three'});

1 Ответ

2 голосов
/ 24 апреля 2020

Компилятор на самом деле не выполняет много высокоуровневого анализа, необходимого для подтверждения присваиваемости типов, которые зависят от пока еще неуказанных параметров типа c generi. Внутри реализации storeThing вы присваиваете значение типа Store<ThingType>[T] переменной типа Container<T> | undefined, где T еще не определено как конкретный тип. И, к сожалению, компилятор не может видеть их как совместимые без указания T, даже если они совместимы для всех возможных сужений T.

Это, по сути, ограничение дизайна TypeScript. См. microsoft / TypeScript # 36737 и microsoft / TypeScript # 36349 для похожих проблем такого рода; в обоих случаях компилятор не может следовать связи более высокого порядка между индексированным доступом generi c и другим совместимым типом. Существует существующее открытое предложение, microsoft / TypeScript # 33014 , для лучшей обработки подобных случаев, но неясно, будет ли что-либо реализовано там.

До тех пор, пока это не произойдет нам нужно найти какой-то путь вперед.


Разумный способ двигаться дальше: как только вы хорошо и по-настоящему убедили себя, что то, что вы делаете, совершенно безопасно (и будьте осторожны, это легко чтобы ошибиться и подумать, что что-то безопасно, когда это не так), целесообразно разумное использование утверждения типа :

const container = myStore[thing.type] as Container<T> | undefined; // okay

Этот тип вещи предназначен для утверждения типа: ситуации, в которых вы знаете о типе больше, чем может проверить компилятор. Вы просто утверждаете , что то, что вы делаете, безопасно, и идете дальше. Конечно, есть некоторая опасность, что вы солгали компилятору ... или что ваш код изменится в будущем и превратит ранее правильное утверждение типа в al ie. Утверждения типа переносят бремя проверки безопасности типов с компилятора на разработчика, что хорошо, если вы готовы принять эту ответственность.


Другой способ продолжить: найти некоторые манипуляции с типами, которые достаточно самодостаточны, чтобы компилятор мог следовать вашим рассуждениям даже с неопределенным обобщением c. Это немного искусства, и это не всегда возможно. Вот что я хотел бы сделать, это двухэтапный процесс:

const myStoreNarrowed: Store<T> = myStore; // okay
const container: Container<T> | undefined = myStoreNarrowed[thing.type]; // okay

Компилятор может распознать, что безопасно назначить myStore переменной типа Store<T>, которая уже Store<ThingType>. И затем он может распознать, что когда вы индексируете в Store<T> с помощью клавиши T, вы получаете что-то назначаемое для Container<T> | undefined.

Я бы, вероятно, использовал этот подход, поскольку он все еще дает некоторые гарантии безопасности типов, которые теряются при использовании утверждения типа. Но если ничего не помогает, всегда есть (тщательно продуманное) утверждение типа.


Хорошо, надеюсь, это поможет; удачи!

Детская площадка ссылка на код


...