Скажем, у меня есть следующие типы:
type MessageType = 'example1' | 'example2' | 'example3'
type MessageHead = {
+type: MessageType
}
type BaseBody = {
+payload?: any,
+data?: any
}
type LabelledBody = {
+labelName: string
}
type MessageBody = BaseBody | LabelledBody
type Message = MessageHead & MessageBody
Затем я потребляю такое сообщение:
[{name: 'example1'}, {name: 'potato'}].find(thing => thing.name === message.labelName)
В результате возникает следующее исключение потока:
Cannot get message.labelName because:
• all branches are incompatible:
• Either property labelName is missing in MessageHead [1].
• Or property labelName is missing in BaseBody [2].
• ... 1 more error.
Когда type Message = MessageHead & MessageBody отображается как нарушенный тип
Я не понимаю, почему мой тип союза не позволяет отправлять сообщения с именем ярлыка?
Обновлено: ссылка Tryflow: Ссылка Tryflow



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Ваш тип объединения делает допускает использование Message с именем метки. Проблема в том, что он также позволяет использовать для Messageбез имя метки. Рассмотрим союз, который у вас есть в этой строке:
type MessageBody = BaseBody | LabelledBody
Это означает, что MessageBody может выглядеть так:
type BaseBody = {
+payload?: any,
+data?: any
}
или же
type LabelledBody = {
+labelName: string
}
Сделав еще один шаг, мы можем выполнить пересечение MessageBody и MessageHead и увидеть, что форма Message может быть в одном из этих двух случаев:
(Дело 1)
{
+type: MessageType, // From MessageHead
+payload?: any, // From BaseBody
+data?: any // From BaseBody
}
(случай 2)
{
+type: MessageType, // From MessageHead
+labelName: string // From LabelledBody
}
Поэтому, когда Flow видит, что вы обращаетесь к labelName объекта message, он считает (правильно), что объект message может выглядеть как случай 1 (см. Выше). Если мы в случае 1, тогда вы не можете получить доступ к labelName, так как он не существует, поэтому он выдает ошибку. Самый простой способ обойти это - создать два типа сообщений, один со свойствами labelName и другой со свойствами payload и data. Затем вы можете аннотировать свою функцию как получение одного из типов:
(Пытаться)
type MessageWithLabel = MessageHead & LabelledBody
const exFunc = (message: MessageWithLabel) => {
[{name: 'example1'}, {name: 'potato'}].find(thing => thing.name === message.labelName)
}
В качестве альтернативы вы можете использовать несвязный союз, чтобы сообщить потоку, какой случай вы используете. Эта стратегия включает установку свойства (например, type), которое сообщает потоку, с каким типом объекта мы имеем дело.
(Пытаться)
type MessageWithBody = {|
+type: 'base',
+payload?: any,
+data?: any
|}
type MessageWithLabel = {|
+type: 'labelled',
+labelName: string
|}
type Message = MessageWithBody | MessageWithLabel
const exFunc = (message: Message) => {
if (message.type === 'labelled') {
const labelName = message.labelName
return [{name: 'example1'}, {name: 'potato'}].find(thing => thing.name === labelName)
// Sidenote:
// I had to extract labelName to a constant for Flow to typecheck this correctly.
// So if we used thing.name === message.labelName Flow will throw an Error.
// If you don't extract it right away, flow thinks the Message may have changed
// after pretty much any non-trivial action. Obviously it doesn't change in this
// example, but, hey, Flow isn't perfect.
// Reference: https://flow.org/en/docs/lang/refinements/#toc-refinement-invalidations
} else {
// Do something for the 'base' type of message
}
}
Пару недель назад мы боролись с аналогичной проблемой и в конце концов сдались, переключившись на более простой шаблон, в котором мы не пытались «расширять» типы, а просто использовали один тип с дополнительными параметрами там, где это необходимо. Не так хорошо, но отлично работало.