#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
?
Пример А
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;
}
});
Пример В
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;
});