Почему Typescript не выводит тип в операторах Switch поверх глубоких / вложенных свойств

#typescript

#машинописный текст

Вопрос:

Почему вывод типа работает в примере A, но не в B? Единственное отличие заключается в позиции типа string . block.type против block.meta.type . A компилирует и выводит тип, а B приводит к.

 // Example B errors
Property 'a' does not exist on type 'Block'. Property 'a' does not exist on type 'ITwo'.
Property 'b' does not exist on type 'Block'. Property 'b' does not exist on type 'IOne'.
 

Как мне заставить B правильно компилироваться и делать выводы, не изменяя структуру данных IOne or ITwo ?


Пример А

https://www.typescriptlang.org/play ?#code/KYOwrgtgBAKgmgBQKJQN5QPIDkUF4oDkA9iMAQDSwDqGU BALgO5EEC AUBwJYgPAAnAGYBDAMbAoASQyk0HKIqgiAXFADODAbwDm5BUoYBPAA7A18ZADpsSDpx59BoidJgt5SqACM1m7SB6BorGZhaISFYwNPZcoZIAQgA2RGIA1nTSspIAPm4sXADaALpWECImABSV3inpKsmpaQCUdAB8nkrqTNwMYgAWUDV1aVbxrajBXlBiIuqSlpG2KlPTSrVNViKraz4CwCJpANw7irPzsBFRNCu70xvpVt5Qp-f7hydenGzNR0A

 enum TYPE { ONE = 'one', TWO = 'two'}

interface IOne {
    a: string,
    type: TYPE.ONE
}

interface ITwo {
    b: string,
    type: TYPE.TWO
}

type Block = IOne | ITwo

[].map((block:Block) => {
    switch (block.type) {
        case TYPE.ONE:
            block.a
            break;
        case TYPE.TWO:
            block.b 
            break;
    }
});
 

Пример В

https://www.typescriptlang.org/play ?#code/KYOwrgtgBAKgmgBQKJQN5QPIDkUF4oDkA9iMAQDSwDqGU BALgO5EEC AUBwJYgPAAnAGYBDAMbAoASQyk0HKIqgiAXFADODAbwDm5BUojAGIqGtQGlShgE8ADsDXxkAOmxJLUTpx59BoiWkYFnkrACM1TW0QPU8jEzNQq2t7R1hEJBcYGk9vLlsHKAAhABsiMQBrOmlZSQAfIJYuAG0AXRcIETsACm6wssqVUvKKgEo6AD4kxXUmbgYxAAsoPoGKjuMRFwLgcYtkpTERdUlnTPcVTwPFfpGXESvrsIFgEQqAbkfFI5P012yMJdrslbpUXGEoF8QS83p8rN5Ru8gA

 enum TYPE { ONE = 'one', TWO = 'two'}

interface IOne {
    a: string,
    meta : {
        type: TYPE.ONE
    }
}

interface ITwo {
    b: string,
    meta : {
        type: TYPE.TWO
    }
}

type Block = IOne | ITwo

[].map((block:Block) => {
    switch (block.meta.type) {
        case TYPE.ONE:
            block.a
            break;
        case TYPE.TWO:
            block.b 
            break;
    }
});
 

Заранее спасибо, Джей.

Ответ №1:

Это обсуждается и отслеживается в разделе проблема с вложенными объединениями тегов в репозитории TS. Краткий ответ на ваш вопрос: вы не сможете делать то, что хотели, пока проблема не будет устранена.

Защита типов и обобщения

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

Поскольку обсуждаемый нами пример довольно абстрактен, это может быть непрактичным решением для вашего реального кода.

Предполагая, что существует всего несколько типов, имеет смысл избавиться от оператора switch в пользу условных возвратов («Возвращать рано, возвращать часто»). Это сделало бы использование type guard тривиальным:

 enum TYPE { ONE = 'one', TWO = 'two'}

interface IOne {
    a: string,
    meta : {
        type: TYPE.ONE
    }
}

interface ITwo {
    b: string,
    meta : {
        type: TYPE.TWO
    }
}

type Block = IOne | ITwo;

export const isBlock = <T extends Block>(
  b: Block,
  metaType: TYPE,
): b is T =>
  b.meta.type === metaType;

[].map((block: Block) => {
    if (isBlock<IOne>(block, TYPE.ONE)) {
        return block.a;
    }
    
    return block.b; 
});