Я собираюсь назвать это ограничением / отсутствующей функцией дизайна: 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
}
Хорошо, надеюсь, это поможет; удачи!
Детская площадка ссылка на код