Оператор '!==' нельзя применять к типам A | Б, а '===' может?

Я прочитал оператор '==' не может быть применен к типам x и y в Typescript 2, и это не было информативным для моего случая.

В TypeScript 2.5.3 мы определяем многие модели, используя строковые перечисления в форме:

export interface Event {
   category: "MORNING" | "NIGHT";
   enabled: boolean;
}

А затем примените к ним компараторы, например:

morning = events.filter(event => event.category === 'MORNING');

без жалоб.

Теперь в этом фрагменте кода:

if (event.enabled || event.category === 'MORNING') {
 // something
}
else if (event.category !== 'MORNING') {
 // something else
}

Я получаю ошибку компиляции Operator '!==' cannot be applied to types '"MORNING"' and '"NIGHT"' в условии else if, но не в условии if, в котором используется тот же (но противоположный) компаратор.

Сокращая пример дальше, компилируется следующее:

if (event.category !== 'MORNING') {
 // something
}
else if (event.category !== 'MORNING') {
 // something else
}

И это компилируется:

if (event.category !== 'MORNING') {
 // something
}
else if (event.category === 'MORNING') {
 // something else
}

Принимая во внимание, что это выдает ошибку (в строке else if):

if (event.category === 'MORNING') {
 // something
}
else if (event.category !== 'MORNING') {
 // something else
}

Какое фундаментальное свойство средства проверки типов я неправильно понял?

(Примечание: последние примеры сокращены из более сложных ситуаций, поэтому я не могу использовать простой else.)


person msanford    schedule 03.11.2017    source источник
comment
Ваше условие else if не имеет смысла. Условие if равно event.enabled || event.category === 'MORNING', поэтому по определению, если это условие не было истинным, то по определению event.category равно 'NIGHT', а event.category !== 'MORNING' истинно. Почему есть необходимость проверить это еще раз? Если это не точное представление условия, которое вам действительно нужно проверить, приведите пример, который является точным представлением этого условия.   -  person JLRishe    schedule 03.11.2017
comment
@JLRishe прав; Я перепробовал все комбинации, в которых ваше условное выражение не является избыточным и работает. Что-то связанное с сужением типа.   -  person Alex Guerra    schedule 03.11.2017
comment
Это происходит более или менее по той же причине, что и в вопросе, на который вы ссылаетесь.   -  person JLRishe    schedule 03.11.2017
comment
@JLRishe Действительно, но, как я объяснил, это сокращенный (и довольно надуманный) пример из более сложной матрицы решений с другими элементами, которые я постепенно удалял, чтобы прийти к окончательным случаям. В реальном коде category также более сложный, но при создании моего минимально воспроизводимого примера я заметил идентичное поведение с приведенный пример. Добавление настоящей стены кода не добавило бы ценности вопросу, поскольку я только хотел знать, почему существуют эти три последних случая. Ответ, который я благодарен за то, что вы предоставили, - это анализ типов на основе потока управления. :)   -  person msanford    schedule 06.11.2017


Ответы (2)


Вы получаете сообщение об ошибке, потому что компилятор уже знает, что в момент условия else if event.category не является 'MORNING', и больше не позволит вам сравнивать с ним event.category.

Если это условие оценивается как false,

if (event.enabled || event.category === 'MORNING') {
 // something
}

тогда по определению event.category не 'MORNING' и, следовательно, 'NIGHT'. Компилятор не позволит вам снова сравнить его с 'MORNING' (да и нет смысла снова сравнивать его с 'MORNING'), потому что уже известно, что это не 'MORNING' в момент, когда оценивается условие else if.

Между прочим, следующее приводит к ошибке компиляции по той же причине:

if (event.category !== 'MORNING') {
 // something
}
else if (event.category !== 'NIGHT') {
 // something else
}

Как и это:

if (event.category === 'MORNING' || event.category !== 'MORNING') {
 // something
}

Это связано с тем, что TypeScript «сужает» типы объединения при оценке последующих логических выражений.

Как предлагается в комментариях ниже, см. https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-0.html#control-flow-based-type-analysis, а также https://github.com/Microsoft/TypeScript/blob/master/doc/spec.md#4.24

person JLRishe    schedule 03.11.2017
comment
Я был в середине написания более подробной версии этого, но теперь я сдаюсь! Возможно, вы захотите упомянуть, что это связано с анализ типов на основе потока управления, что позволяет сузить event.category. Ваше здоровье! - person jcalz; 03.11.2017

Я думаю, что это правильно и связано с системой проверки типов в Typescript и TypeGuards. Рассмотреть возможность:

Ваш первый пример:

if (event.category !== 'MORNING') {
 // something
}
else if (event.category === 'MORNING') {
 // something else
}

После первого if машинописный текст ничего не может предположить о типе category. Простая проверка, если что-то не равно какому-либо значению, ничего не говорит нам о типе переменной. В следующих цепочках else if это может быть все, что соответствует типу вашей переменной.

Не рабочий пример:

if (event.category === 'MORNING') {
 // something
}
else if (event.category !== 'MORNING') {
 // something else
}

Но если вы проверите наоборот, вы точно знаете, что category не может быть равно 'MORNING', поэтому Typescript меняет ваш тип. Своего рода сужение.

https://github.com/Microsoft/TypeScript/blob/master/doc/spec.md#424-type-guards Подробнее в спецификации C:

person Kacper Wiszczuk    schedule 03.11.2017
comment
Спасибо за объяснение! - person msanford; 06.11.2017