【问题标题】:Check if value exists in enum in TypeScript检查 TypeScript 的枚举中是否存在值
【发布时间】:2017-10-03 22:15:13
【问题描述】:

我收到一个号码 type = 3 并且必须检查它是否存在于这个枚举中:

export const MESSAGE_TYPE = {
    INFO: 1,
    SUCCESS: 2,
    WARNING: 3,
    ERROR: 4,
};

我发现最好的方法是将所有枚举值作为一个数组并在其上使用 indexOf。但是生成的代码不是很清晰:

if( -1 < _.values( MESSAGE_TYPE ).indexOf( _.toInteger( type ) ) ) {
    // do stuff ...
}

有没有更简单的方法?

【问题讨论】:

  • if(Object.values(MESSAGE_TYPE).includes(+type)?你无能为力。
  • 这在 ES6 中有效,但在 ES5 中无效
  • @TimSchoch 你可以做!!MESSAGE_TYPE[type]来检查一个值是否存在。如果type 的值在MESSAGE_TYPE 上不存在,MESSAGE_TYPE[type] 将返回 undefined
  • @Kevin Babcock 但是,其中一个枚举值映射到0 将失败。
  • @Ingo Bürk 好点!我想可以进行明确的检查MESSAGE_TYPE[type] !== undefined

标签: javascript typescript enums


【解决方案1】:

这仅适用于非常量、基于数字的枚举。对于 const 枚举或其他类型的枚举,请参阅this answer above


如果您使用的是 TypeScript,则可以使用 actual enum。然后您可以使用in 进行检查。

export enum MESSAGE_TYPE {
    INFO = 1,
    SUCCESS = 2,
    WARNING = 3,
    ERROR = 4,
};

var type = 3;

if (type in MESSAGE_TYPE) {

}

这是因为当你编译上面的枚举时,它会生成下面的对象:

{
    '1': 'INFO',
    '2': 'SUCCESS',
    '3': 'WARNING',
    '4': 'ERROR',
    INFO: 1,
    SUCCESS: 2,
    WARNING: 3,
    ERROR: 4
}

【讨论】:

  • 这只适用于正确的枚举,对吧?目前它是这样定义的:export const MESSAGE_TYPE = { ... }
  • 是的。仅使用适当的枚举。
  • 好的,感谢您的解释。我会检查为什么我们没有使用正确的枚举,看看我们是否可以改变它。
  • 这不适用于字符串枚举,因为它们没有反向映射:typescriptlang.org/docs/handbook/release-notes/…
  • 这种方法似乎在 2021 年适用于字符串枚举。
【解决方案2】:

如果您希望它与字符串枚举一起使用,您需要使用Object.values(ENUM).includes(ENUM.value),因为根据https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-4.html,字符串枚举不是反向映射的:

Enum Vehicle {
    Car = 'car',
    Bike = 'bike',
    Truck = 'truck'
}

变成:

{
    Car: 'car',
    Bike: 'bike',
    Truck: 'truck'
}

所以你只需要这样做:

if (Object.values(Vehicle).includes('car')) {
    // Do stuff here
}

如果您收到错误消息:Property 'values' does not exist on type 'ObjectConstructor',那么您的目标不是 ES2017。你可以使用这个 tsconfig.json 配置:

"compilerOptions": {
    "lib": ["es2017"]
}

或者你可以做一个 any 演员:

if ((<any>Object).values(Vehicle).includes('car')) {
    // Do stuff here
}

【讨论】:

  • JSONLint 显示 Property 'values' does not exist on type 'ObjectConstructor'
  • @BBaysinger in typescript 试试这个:(&lt;any&gt;Object).values(Vehicle).includes(Vehicle.car)
  • 我相信这不是这个问题的答案。您的解决方案 (Object.values(Vehicle).includes(Vehicle.car)) 将始终为真,但问题是如何检查给定值是否包含在枚举中,例如 (Object.values(Vehicle).includes('car')) 应返回 true(Object.values(Vehicle).includes('plane')) 应返回 false。
  • Object.values(Vehicle).includes('car') 但是警告 Argument of type 'string' is not assignable to parameter of type 'Vehicle' 所以你还必须输入 assert
  • Object.values&lt;string&gt;(Enum).includes(value) 为我工作
【解决方案3】:
export enum UserLevel {
  Staff = 0,
  Leader,
  Manager,
}

export enum Gender {
  None = "none",
  Male = "male",
  Female = "female",
}

日志中的差异结果:

log(Object.keys(Gender))
=>
[ 'None', 'Male', 'Female' ]

log(Object.keys(UserLevel))
=>
[ '0', '1', '2', 'Staff', 'Leader', 'Manager' ]

解决方案,我们需要将键删除为数字。

export class Util {
  static existValueInEnum(type: any, value: any): boolean {
    return Object.keys(type).filter(k => isNaN(Number(k))).filter(k => type[k] === value).length > 0;
  }
}

用法

// For string value
if (!Util.existValueInEnum(Gender, "XYZ")) {
  //todo
}

//For number value, remember cast to Number using Number(val)
if (!Util.existValueInEnum(UserLevel, 0)) {
  //todo
}

【讨论】:

    【解决方案4】:

    您的问题有一个非常简单易行的解决方案:

    var districtId = 210;
    
    if (DistrictsEnum[districtId] != null) {
    
    // Returns 'undefined' if the districtId not exists in the DistrictsEnum 
        model.handlingDistrictId = districtId;
    }
    

    【讨论】:

    • 感谢 Ester 的回答。由于我已经从编程转向全职 UX 设计,我无法再验证这一点。 @crowd,如果我接受的答案仍然是 2019 年要走的路,请告诉我!干杯
    • @TimSchoch 我可以确认这至少对于数字枚举非常有效。这是最优雅的解决方案恕我直言。
    • @PatrickP。您能否确认 Ester 提出的解决方案也适用于字符串枚举?
    • @TimSchoch 是的!它也适用于字符串。就像字典一样 - 您可以使用任何类型的字典中的键。
    • 请注意,如果枚举使用具有与枚举成员名称不同的值的字符串初始值设定项,则这不适用于字符串枚举。请参阅下面@Xiv 的回答:stackoverflow.com/a/47755096/4752920
    【解决方案5】:
    export enum YourEnum {
       enum1 = 'enum1',
       enum2 = 'enum2',
       enum3 = 'enum3',
    }
    
    const status = 'enumnumnum';
    
    if (!Object.values(YourEnum)?.includes(status)) {
         throw new UnprocessableEntityResponse('Invalid enum val');
    }
    

    【讨论】:

    • 我最喜欢这个
    • 所以这个例子只是使用 key==value 这就是它起作用的原因,对吧?如果 key!=value,它会按键检查。
    • 其实这个案例只是因为巧合而已。 'enum1' 只会被发现,因为它与键的值相同。但是,如果键与值不同,则不起作用。
    • @lukas_o 是对的。这个解决方案乍一看很清楚,但它肯定容易出错。
    • 是的,这是一个不好的例子,不应按原样使用。 in 关键字匹配键,而不是值,因此您需要更好的 if 语句解决方案,例如 Object.values(YourEnum).includes(status) 或 ES5 的 indexOf 解决方案。
    【解决方案6】:
    enum ServicePlatform {
        UPLAY = "uplay",
        PSN = "psn",
        XBL = "xbl"
    }
    

    变成:

    { UPLAY: 'uplay', PSN: 'psn', XBL: 'xbl' }
    

    所以

    ServicePlatform.UPLAY in ServicePlatform // false
    

    解决方案:

    ServicePlatform.UPLAY.toUpperCase() in ServicePlatform // true
    

    【讨论】:

    • 只有在枚举中的键和值是相同的大写/小写文本时才有效
    【解决方案7】:

    According to sandersn 最好的方法是:

    Object.values(MESSAGE_TYPE).includes(type as MESSAGE_TYPE)
    

    【讨论】:

    • 这可能是最好和最安全的答案。它避免使用any。如果您可以保证枚举的键和值相同,type in MESSAGE_TYPE 语法可能会更好,因为它是键查找而不是值查找。
    • 啊哈!在答案中找到了方法,我可以确认此解决方案在没有任何any 或类型投诉的情况下有效,并且当枚举名称本身与其各自的实际值不匹配时它有效(此页面上的许多hacky 解决方案建议或使用)。应该是公认的答案,尤其是最初来自 TypeScript 的 GitHub。
    • 这适用于您需要检查的每种情况。完美的答案。
    【解决方案8】:

    对于任何来到这里希望验证字符串是否是枚举值之一并将其类型转换的人,我编写了这个函数,它返回正确的类型,如果字符串不在枚举中,则返回 undefined

    function keepIfInEnum<T>(
      value: string,
      enumObject: { [key: string]: T }
    ) {
      if (Object.values(enumObject).includes((value as unknown) as T)) {
        return (value as unknown) as T;
      } else {
        return undefined;
      }
    }
    

    举个例子:

    enum StringEnum {
      value1 = 'FirstValue',
      value2 = 'SecondValue',
    }
    keepIfInEnum<StringEnum>('FirstValue', StringEnum)  // 'FirstValue'
    keepIfInEnum<StringEnum>('OtherValue', StringEnum)  // undefined
    

    【讨论】:

      【解决方案9】:

      类型断言是不可避免的。跟进

      enum Vehicle {
          Car = 'car',
          Bike = 'bike',
          Truck = 'truck'
      }
      

      我找到了一个没有提到的替代方案,所以我想分享一下我的解决方法:

      const someString: Vehicle | string = 'car';
      const inEnum = (Object.values(Vehicle) as string[]).includes(someString);
      

      我觉得这更真实,因为我们通常使用类型安全(带有字符串)并希望将其与枚举进行比较;将其转换为any(原因:永远不要这样做)或Vehicle(原因:可能不真实)有点鲁莽。相反,将Object.values() 输出类型转换为字符串数组实际上非常真实。

      【讨论】:

      • 我喜欢使用两行代码: const options: string[] = Object.values(TheEnum); const isInTheEnum = options.includes(theValue);
      【解决方案10】:

      更新:

      我发现每当我需要检查枚举中是否存在值时,我并不真的需要枚举,而类型是更好的解决方案。所以我的原始答案中的枚举变为:

      export type ValidColors =
        | "red"
        | "orange"
        | "yellow"
        | "green"
        | "blue"
        | "purple";
      

      原答案:

      为了清楚起见,我喜欢将 valuesincludes 调用分成不同的行。这是一个例子:

      export enum ValidColors {
        Red = "red",
        Orange = "orange",
        Yellow = "yellow",
        Green = "green",
        Blue = "blue",
        Purple = "purple",
      }
      
      function isValidColor(color: string): boolean {
        const options: string[] = Object.values(ValidColors);
        return options.includes(color);
      }
      

      【讨论】:

      • type ValidColors 的问题是你不能为它写一个isValidColor(color: string): boolean 函数:因为type ValidColors 在运行时不存在,所以没有什么可以检查的。如果您尝试从非类型化 API(例如用户输入)转到 ValidColor 并拒绝无效输入,则会出现问题。
      • @mamacdon,绝对。这就是为什么我也留下了原来的答案。我刚刚注意到,大多数情况下,当我要获取枚举时,是在我正在验证 Vue 组件属性之类的东西的情况下,在这种情况下,类型是有效的。
      【解决方案11】:

      如果你在那里找到如何检查联合是否包含特定值,有解决方案:

      // source enum type
      export const EMessagaType = {
         Info,
         Success,
         Warning,
         Error,
      };
      
      //check helper
      const isUnionHasValue = <T extends number>(union: T, value: T) =>
         (union & value) === value;
      
      
      //tests
      console.log(
       isUnionHasValue(EMessagaType.Info | EMessagaType.Success), 
       EMessagaType.Success);
      
      //output: true
      
      
      console.log(
       isUnionHasValue(EMessagaType.Info | EMessagaType.Success), 
       EMessagaType.Error); 
      
      //output: false
      

      【讨论】:

        【解决方案12】:

        以下函数返回另一个函数,该函数充当输入枚举的类型谓词(假设它是字符串样式的枚举)。

        function constructEnumPredicate<RuntimeT extends string, EnumClass extends {[key: string]: RuntimeT}>(enumClass: EnumClass): (maybeEnum: string) => maybeEnum is EnumClass[keyof EnumClass] {
            const reverseMapping: {[key: string]: boolean} = {};
        
            for (const enumVal in enumClass) {
                const enumStr = enumClass[enumVal];
                reverseMapping[enumStr] = true;
            }
        
            function result(maybeEnum: any): maybeEnum is EnumClass[keyof EnumClass] {
                return !!reverseMapping[maybeEnum];
            }
        
            return result;
        }
        

        它适用于 TypeScript 4.2.4,但我没有测试过早期版本。

        主要有趣的部分是EnumClass[keyof EnumClass] 返回类型。当这种类型是 TypeScript 中的枚举时,它返回枚举的原始类型,其中 EnumClass 是运行时枚举类的类型。

        关于如何使用这种构造的示例,假设我们有以下枚举:

        enum Direction {
            Left = "<-",
            Right = "->"
        }
        

        Direction 既是类型又是运行时对象。我们可以为 Direction 生成一个类型谓词并像这样使用它:

        const isDirection = constructEnumPredicate(Direction);
        function coerceDirection(maybeDir: string): Direction {
            // Since we make a type predicate rather than just a normal predicate,
            // no explicit type casting is necessary!
            return isDirection(maybeDir) ? maybeDir : Direction.Left;
        }
        

        【讨论】:

        • 谢谢,这就是我想要的。一个问题:reverseMapping 的目的是什么?
        • @DavidGood 只是更容易转换为存在检查。您还可以通过检查输入是否存在于 enumClass 中来使用 Set 甚至可能是 enumClass 本身。
        猜你喜欢
        • 2021-05-12
        • 2015-02-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-03-12
        • 1970-01-01
        相关资源
        最近更新 更多