Отсутствующие свойства Union Type

Скажем, у меня есть следующие типы:

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

Пару недель назад мы боролись с аналогичной проблемой и в конце концов сдались, переключившись на более простой шаблон, в котором мы не пытались «расширять» типы, а просто использовали один тип с дополнительными параметрами там, где это необходимо. Не так хорошо, но отлично работало.

Ben Clayton 18.03.2018 22:32
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
6
1
1 161
1

Ответы 1

Ваш тип объединения делает допускает использование 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
  }
}

Другие вопросы по теме