Отсутствует поток в сужении типов с «неизвестным» типом данных в машинописи - PullRequest
1 голос
/ 27 апреля 2020

Я играл с неизвестным типом. Минимальный пример:

let value: unknown;
if (value === undefined || value === null || value === '') {
  typeof value;    //  ""|null|undefined  fine
} else if (typeof value === 'object') {
  typeof value;     // object | null  huh? 
}

Это ограничение дизайна или ошибка в машинописи? Я не смог найти что-то подобное в трекере проблем.

1 Ответ

3 голосов
/ 27 апреля 2020

Я собираюсь назвать это ограничением / отсутствующей функцией дизайна: TypeScript в настоящее время не имеет типов вычитания или отрицательных типов , и хотя в какой-то момент работа была сделано здесь ( microsoft / TypeScript # 29317 ), было на полках . Это означает, что не существует общего способа взять два типа A и B и представить тип "все, что может быть присвоено A, но не B", иначе, A - B или A \ B или A & not B, и др c. Если A и B являются типами объединения, где каждый член B является членом A, тогда вы можете использовать условный тип, например Exclude<A, B>, но только работает для типов объединения.

Поскольку unknown не является типом объединения, в TypeScript нет способа представить тип unknown & not (undefined | null | ""), который должен компилировать value в else. В предложении else сужения не происходит, а value остается unknown. Сожалею. 10

Поскольку value по-прежнему относится к типу unknown в предложении else, проверка typeof value === 'object' может сужаться только от unknown к типам, где typeof value === 'object' является истинным: это object | null.


Это может быть неудовлетворительно: почему является типом unknown, рассматриваемым как не-объединение, в отличие от внутреннего представления, скажем, {} | null | undefined или object | string | number | boolean | null | undefined? В конце концов, вам будет сложно найти значение типа unknown, которое нельзя назначить ни одному из этих других типов (например, "hello" можно присвоить {}). И действительно, если вы измените аннотированный тип value на object | string | number | boolean | null | undefined, вы получите желаемое поведение:

let value: object | string | number | boolean | null | undefined;
if (value === undefined || value === null || value === '') {
    typeof value;    //  ""|null|undefined 
} else if (typeof value === 'object') {
    typeof value;     // object
}

Так почему же unknown просто так не действует время? Ближайший ответ к окончательному ответу содержится в комментарии к связанному вопросу одного из отведений TS:

Было долгое внутреннее обсуждение того, нужны ли нам вообще unknown, поскольку он имеет домен, идентичный {} | null | undefined - было даже предложено просто написать его как псевдоним типа в lib.d.ts. Но мы явно хотели тип, который не не распространял бы по условным типам, потому что это расширение обычно делало вещи хуже, а не лучше.

То, что unknown обрабатывается как объединение, вызывает странные нижестоящее поведение с дистрибутивными условными типами . Любая языковая функция, которая разделяет типы на объединения подтипов, имеет наблюдаемые эффекты, некоторые из которых могут не соответствовать вашим ожиданиям. Я видел, как люди сталкивались с тем фактом, что boolean на самом деле определяется как объединение true | false и что он распадается на части, когда люди ожидают, что этого не произойдет. В общем, это сложная вещь, и любой сделанный выбор, как правило, является компромиссом, который оставляет несчастными, по крайней мере, некоторых людей.


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

function toUnion(
    x: unknown
): asserts x is object | string | number | boolean | null | undefined { }

И тогда вы звоните toUnion(value) перед проверкой:

let value: unknown;
toUnion(value); // does nothing at runtime but transforms value to the union type
if (value === undefined || value === null || value === '') {
    typeof value;    //  ""|null|undefined 
} else if (typeof value === 'object') {
    typeof value;     // object
}

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...