Как избежать разрушения упакованного типа объединения - PullRequest
2 голосов
/ 27 июня 2019

Мне кажется, что Typescript сворачивает упакованный объединенный тип (нежелательный), когда он узнает начальное значение. Я не уверен, что это ошибка, но мне интересно, есть ли способ ее избежать.

Пример:

type Prime = 3|5|7;
type OrDonuts<T> = T | 'donuts';

function compare<T>(a: T, b: OrDonuts<T>) {
    return a == b;
}

let value: Prime;
compare(value, 3);
// OK!

value = 5;
compare(value, 3);
// TS2345: Argument of type '5' is not assignable to parameter of type 'OrDonuts<3>'

Чтобы обойти эту ошибку, я должен явным образом свернуть, сказав что-то вроде value = 5 as Prime;.

Это ошибка, ожидаемое поведение, я просто делаю это неправильно?

(узел: 10.15, машинопись: 3.5.1)

Ответы [ 2 ]

3 голосов
/ 27 июня 2019

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

Это очень часто желательно, так как он поддерживает следующие варианты использования:

let x: number | string = Math.random() < 0.5 ? "hello" : "goodbye"; // x is string now
if (x.length < 6) { // no error here
  x = 0; // the widest x can be is string | number, so this assignment is fine
}

Обратите внимание, что даже если вы аннотировали , что x является number | string, компилятор понимает, что это определенно будет string после первоначального назначения. Так что он не жалуется, когда вы проверяете x.length (как если бы x мог быть number). Это настолько полезное поведение, что его отключение может привести к множеству реальных поломок кода TypeScript.

К сожалению, он также отвечает за поведение, которое вы здесь видите. После присвоения 5 value компилятор видит value как содержащую переменную суженного типа 5. Не Prime. Вы всегда можете расширить value до Prime, но компилятор не сделает этого автоматически. Он думает, что помогает вам, предупреждая, что вы звоните compare(5 as 5, 3), что запрещено.

В этом случае, как вы видели, единственный способ переопределить это поведение - это утверждение типа . Вы можете сделать это утверждение либо при первоначальном назначении, либо внутри вызова compare():

let value2: Prime = 5 as Prime
compare(value2, 3); // okay

let value3: Prime = 5;
compare(value3 as Prime, 3); // okay

Или вы можете вручную указать общий тип T при вызове compare(), который также работает:

let value4: Prime = 5;
compare<Prime>(value4, 3); // okay

Вам доступен любой из этих вариантов.

Наиболее каноническим источником документации, которую я могу найти для этого, является Microsoft / TypeScript # 8513 , и конкретно этот комментарий .

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

Ссылка на код

1 голос
/ 27 июня 2019

Предполагаемое поведение Типы объединения .Объявляя

type Prime = 3|5|7; // it is either 3 OR 5 OR 7 but never all of them at the the same time

, вы сообщаете ts-компилятору, что Prime является одним из значений.Теперь вы присваиваете 5 типизированному полю value.

value = 5; // type Prime is now 5
compare(value, 3); // <T> is inferred by the ts-compiler as 5

Чтобы исправить это, вы должны использовать value:Prime или набрать assert, как вы это сделали.


Без вывода типа вы могли бы передать value например

value = 5;
compare<2>(value, 3); // Argument of type '5' is not assignable to parameter of type '2'.

или если бы вы удовлетворяли параметру функции compare в более общем виде, например

let test = 5;
compare(test, 3); // <T> is now number which 5 and 3 are.
...