在 TypeScript 中将字符串转换为枚举

Casting strings to enum in TypeScript

在 TypeScript 中使用枚举时,我 运行 遇到了一个小问题。我的场景是这样的:

问题是,即使在检查方法中是否传入 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 playground 上找到它。

这里发生了一些事情。一个是 TypeScript 不理解 Object.values(x).includes(y)type guard on y. It doesn't match the built-in ways that the compiler tries to narrow types, such as typeof, instanceof, or in checks. To help the compiler out, you can use a user-defined type guard 来表达这种检查方式:

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 基本上会放弃缩小范围。编译器付出了相当大的努力来理解控制流对变量类型的影响,但是 it's not perfect. So, even though we understand that assigning AllowedValues.LOREM_IPSUM to value makes it an AllowedValues, the compiler gives up 并假定它是其原始注释类型,即 string.

处理this的方法是创建一个编译器理解的新变量,它永远不会是AllowedValues。最直接的方法是将其设为 const 变量,如下所示:

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

在上面,新变量 allowedValue 被推断为 AllowedValues 因为如果类型保护成功(此时 valueAllowedValues),如果类型保护失败,则为 AllowedValues.LOREM_IPSUM。无论哪种方式,allowedValue 都是 AllowedValues.


因此,如果您想帮助编译器理解事物,那将是我建议的更改。希望有所帮助。祝你好运!