Я столкнулся с небольшой проблемой при работе с перечислениями в TypeScript. Мой сценарий таков:

  • Я определил перечисление строк, содержащее допустимые значения
  • Я определил метод, который принимает любое входящее значение (типа string), и должен преобразовать его в указанное перечисление

Проблема в том, что даже после проверки того, что входящий value из метода, intellisense сообщает мне, что value по-прежнему является типом string вместо перечисления. Как я могу заставить value быть разновидностью AllowedValues?

Вот пример подтверждения концепции:

/** enum */
enum AllowedValues {
    LOREM_IPSUM = 'lorem ipsum',
    DOLOR_SIT = 'dolor sir',
    AMET = 'amet'
}

/** @method */
function doSomething(value: string = AllowedValues.LOREM_IPSUM) {

    // If value is not found in enum, force it to a default
    if (!(Object as any).values(AllowedValues).includes(value))
        value = AllowedValues.LOREM_IPSUM;

    // Value should be of type `AllowedValues` here
    // But TypeScript/Intellisense still thinks it is `string`
    console.log(value);
}

doSomething('amet');    // Should log `amet`
doSomething('aloha');   // Should log `lorem ipsum`, since it is not found in `AllowedValues`

Вы также можете найти его на Площадка для TypeScript.

0
Terry 5 Окт 2018 в 11:53

1 ответ

Лучший ответ

Здесь происходит несколько вещей. Во-первых, TypeScript не понимает, что Object.values(x).includes(y) - это защита типа в y. Он не соответствует встроенным способам, которыми компилятор пытается сузить типы, таким как typeof, instanceof или in проверки. Чтобы помочь компилятору, вы можете использовать определяемый пользователем тип защиты, чтобы выразить этот способ проверки:

function isPropertyValue<T>(object: T, possibleValue: any): possibleValue is T[keyof T] {
  return Object.values(object).includes(possibleValue);
}

declare function onlyAcceptAllowedValues(allowedValue: AllowedValues): void;
declare const v: string;
if (isPropertyValue(AllowedValues, v)) {
  onlyAcceptAllowedValues(v); // v is narrowed to AllowedValues; it works!
}

Итак, давайте сначала изменим вашу функцию на это:

function doSomething(value: string = AllowedValues.LOREM_IPSUM) {    
  if (!(isPropertyValue(AllowedValues, value)))
    value = AllowedValues.LOREM_IPSUM;

  // TypeScript/Intellisense still thinks it is `string`
  console.log(value);
}

Ой, все еще не работает.


Происходит второе: если вы переназначаете значение переменной, TypeScript по существу отказывается от ее сужения. Компилятор прилагает значительные усилия, чтобы понять влияние потока управления на типы переменных, но это не идеально < / а>. Итак, хотя мы понимаем, что присвоение AllowedValues.LOREM_IPSUM значению value делает его AllowedValues, компилятор отказывается и предполагает, что это его исходный аннотированный тип, то есть string.

Способ справиться с этим - создать новую переменную, которая, как понимает компилятор, никогда не будет ничем иным, как AllowedValues. Самый простой способ сделать это - сделать ее переменной const, например:

function doSomething(value: string = AllowedValues.LOREM_IPSUM) {    
  const allowedValue = isPropertyValue(AllowedValues, value) ? value : AllowedValues.LOREM_IPSUM;
  console.log(allowedValue);
}

В приведенном выше примере новая переменная allowedValue выводится как AllowedValues, потому что она устанавливается как value, если защита типа завершается успешно (в этот момент value является AllowedValues) или AllowedValues.LOREM_IPSUM, если защита типа не работает. В любом случае allowedValue - это AllowedValues.


Так что это будет мое предложение изменить, если вы хотите помочь компилятору понять вещи. Надеюсь, это поможет. Удачи!

1
jcalz 5 Окт 2018 в 15:02