Переменная типа объединения вызывает ошибку в операторе switch - PullRequest
1 голос
/ 30 января 2020

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

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?

1 Ответ

1 голос
/ 30 января 2020

Возможно, вы неправильно поняли, что 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".

И поэтому в вашем switch / case выражении:

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

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

Даже в вашем wizard случае Компилятор понимает, что когда он достигает оператора switch / case, oscar может быть только "cat" или "dog", а не * 10 56 *:

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;

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

Детская площадка ссылка на код

...