Компилятор использует некоторые эвристики для определения , когда расширять литералы .Одним из них является следующее:
- Тип, выведенный для свойства в литерале объекта, является расширенным литеральным типом выражения, если свойство не имеет контекстного типа, включающего литеральные типы.
Таким образом, по умолчанию "foo" | "bar"
расширяется до string
внутри литерала объекта, назначенного для foobar
.
Обратите внимание на часть, которая говорит "если свойство не имеет контекстного типа, включающего литеральные типы. "Один из способов подсказать компилятору, что тип, такой как "foo" | "bar"
, должен оставаться суженным, это заставить его соответствовать типу с ограничением до string
(или объединению, содержащему его).Ниже приводится вспомогательная функция, которую я иногда использую для этого:
type Narrowable = string | number | boolean | symbol | object |
null | undefined | void | ((...args: any[]) => any) | {};
const literally = <
T extends V | Array<V | T> | { [k: string]: V | T },
V extends Narrowable
>(t: T) => t;
Функция literally()
просто возвращает свой аргумент, но тип имеет тенденцию быть уже.Да, это уродливо ... Я держу это в библиотеке утилит вне поля зрения.
Теперь вы можете сделать:
const foobar = literally({
bar
});
и тип выводится как { bar: "foo" | "bar" }
, как вы ожидали,
Независимо от того, используете ли вы что-то вроде literally()
, я надеюсь, это вам поможет;удачи!