Переменная типа union вызывает ошибку в операторе switch

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

type Animal = 'bird' | 'cat' | 'dog';

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

let oscar: Animal = 'dog';

switch (oscar) {
  case 'bird':
    console.log('tweet');
    break;
  case 'cat':
    console.log('meow');
    break;
  case 'dog':
    console.log('bark');
    break;
}

Этот код приведет к ошибке TypeScript: Type '"bird"' is not comparable to type '"dog"'.ts(2678) (аналог cat). Однако, если я использую явное приведение типа к переменной oscar, он работает без проблем:

switch (oscar as Animal) {
  case 'bird':
    ...
  case 'cat':
    ...
  case 'dog':
    ...
}

Не могли бы вы объяснить мне, почему первые два оператора switch не работают, если я использую явное значение для oscar?

Я мог бы понять ошибку, если бы объявил Оскар константой: const oscar = 'dog';, потому что в этом случае это всегда будет собака и ничего больше. Однако представьте на мгновение, что Оскар мог бы превратиться в кота, если бы волшебник совершил определенное заклинание:

let oscar: Animal = 'dog';

while(true) {
  switch (oscar) {
  case 'bird':
    ...
  case 'cat':
    ...
  case 'dog':
    console.log('bark');

    // here comes the wizard
    if(wizard.performsSpell('makeOscarBecomeACat')) {
      oscar = 'cat';  // that should be valid, because oscar is of type Animal
    }

    break;
  }
}

Я что-то неправильно понимаю в назначении переменной oscar, или это просто ошибка TypeScript?


person Spark Fountain    schedule 30.01.2020    source источник


Ответы (1)


Вы можете неправильно понять, что TypeScript 2.0 и выше имеет функцию под названием анализ типов на основе потока управления, реализованный в microsoft / TypeScript # 8010. Одним из эффектов этой функции является то, что

Присвоение (включая инициализатор в объявлении) значения типа S переменной типа T изменяет тип этой переменной на T, суженный на S в пути кода, который следует за назначением. [...] Тип T, суженный на S, вычисляется следующим образом: [...] Если T является типом объединения, результатом является объединение каждого составляющего типа в T, к которому S принадлежит назначаемый.

Это означает заявление

let oscar: Animal = 'dog';

интерпретируется как: «переменная oscar имеет тип Animal, тип объединения. Ей было присвоено значение типа строкового литерала "dog", поэтому, пока он не будет переназначен, мы будем рассматривать переменную oscar как суженный тип Animal автор "dog", а это всего лишь "dog".

И поэтому в вашем заявлении _18 _ / _ 19_:

case 'bird': // error!
//   ~~~~~~ <-- Type '"bird"' is not comparable to type '"dog"'

Вы получаете сообщение об ошибке при попытке сравнить строковый литерал "bird" со строковым литералом "dog". Компилятор знает, что случай 'bird' невозможен, потому что вы не переназначили oscar на что-то совместимое с 'bird'.

Даже в вашем wizard случае компилятор понимает, что когда он достигает оператора _27 _ / _ 28_, oscar может быть только "cat" или "dog", а не "bird":

case 'bird': // error! 
//   ~~~~~~ <-- Type '"bird"' is not comparable to type '"cat" | "dog"'

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

Если вы хотите, чтобы компилятор не осознавал, что oscar определенно "dog", и знал только, что это Animal (как, скажем, заполнитель, пока вы не напишете код, который действительно позволяет ему быть любым членом Animal), вы можете использовать утверждение типа в самом назначении:

let oscar: Animal = 'dog' as Animal;

Теперь весь ваш другой код будет компилироваться без ошибок. Вы даже можете забыть аннотацию, потому что она вам не помогала:

let oscar = 'dog' as Animal;

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

площадка ссылка на код

person jcalz    schedule 30.01.2020
comment
Отличный ответ, большое спасибо за подробное объяснение узких типов профсоюзов, концепции, которую я не знал раньше, но которая имеет для меня смысл. - person Spark Fountain; 30.01.2020