Enum as Parameter in TypeScript

后端 未结 9 1639
故里飘歌
故里飘歌 2020-12-01 13:18

Isn\'t it possible to set the type of a parameter to an Enum? Like this:

private getRandomElementOfEnum(e : enum):string{
    var length:number = Object.         


        
相关标签:
9条回答
  • 2020-12-01 13:59

    I agree with @Tarh. Enums in TypeScript are just Javascript objects without a common interface or prototype (and if they are const enum, then they are not even objects), so you cannot restrict types to "any enum".

    The closest I could get is something like the following:

    enum E1 {
        A, B, C
    }
    enum E2 {
        X, Y, Z
    }
    
    // make up your own interface to match TypeScript enums
    // as closely as possible (not perfect, though)
    interface Enum {
        [id: number]: string
    }
    
    function getRandomElementOfEnum(e: Enum): string {
       let length = Object.keys(e).length / 2;
       return e[Math.floor((Math.random() * length))];
    }
    

    This works for all enums (without custom initializers), but it would also accept other arrays as input (and then fail because the method body relies on the very specific key structure that TypeScript enums have).

    So unless you have a real need for such a "generic" function, make typesafe functions for the individual enum types (or a union type like E1|E2|E3) that you actually need.

    And if you do have this need (and this might very well be an X-Y-problem that can be solved in a better, completely different way given more context), use any, because you have left typesafe territory anyway.

    0 讨论(0)
  • 2020-12-01 13:59

    Summing up the previous answers with some new syntax - a generic typesafe function, which works with numeric enums as well as string enums:

    function getRandomElementOfEnum<T extends {[key: number]: string | number}>(e: T): T[keyof T] {
      const keys = Object.keys(e);
    
      const randomKeyIndex = Math.floor(Math.random() * keys.length);
      const randomKey = keys[randomKeyIndex];
    
      // Numeric enums members also get a reverse mapping from enum values to enum names.
      // So, if a key is a number, actually it's a value of a numeric enum.
      // see https://www.typescriptlang.org/docs/handbook/enums.html#reverse-mappings
      const randomKeyNumber = Number(randomKey);
      return isNaN(randomKeyNumber)
        ? e[randomKey as keyof T]
        : randomKeyNumber as unknown as T[keyof T];
    }
    
    0 讨论(0)
  • 2020-12-01 14:05

    May be this trick will fit:

    enum AbstractEnum { // put somewhere in hidden scope
    }
    
    private getRandomElementOfEnum(e: typeof AbstractEnum) {
        ...
    }
    
    0 讨论(0)
  • 2020-12-01 14:07

    Tested on TypeScript 3.9.7

    Solution

    type EnumTypeString<TEnum extends string> =
        { [key in string]: TEnum | string; }
    
    type EnumTypeNumber<TEnum extends number> =
        { [key in string]: TEnum | number; }
        | { [key in number]: string; }
    
    type EnumType<TEnum extends string | number> =
        (TEnum extends string ? EnumTypeString<TEnum> : never)
        | (TEnum extends number ? EnumTypeNumber<TEnum> : never)
    
    type EnumOf<TEnumType> = TEnumType extends EnumType<infer U>
        ? U
        : never
    

    Usage

    EnumType:
    function forEachEnum<TEnum extends string | number>(
        enumType: EnumType<TEnum>,
        callback: (value: TEnum, key: string) => boolean|void,
    ) {
        for (let key in enumType) {
            if (Object.prototype.hasOwnProperty.call(enumType, key) && isNaN(Number(key))) {
                const value = enumType[key] as any
                if (callback(value, key)) {
                    return
                }
            }
        }
    }
    
    EnumOf:
    function forEachEnum2<TEnumType>(
        enumType: TEnumType,
        callback: (value: EnumOf<TEnumType>, key: string) => boolean|void,
    ) {
        for (let key in enumType) {
            if (Object.prototype.hasOwnProperty.call(enumType, key) && isNaN(Number(key))) {
                const value = enumType[key] as any
                if (callback(value, key)) {
                    return
                }
            }
        }
    }
    

    Tests

    enum EnumAsString {
        Value1 = 'value 1',
        Value2 = 'value 2',
    }
    
    enum EnumAsNumber {
        Value1 = 1,
        Value2 = 2,
    }
    
    // Error
    let sn: EnumType<string> = EnumAsNumber
    
    // Correct
    let ns: EnumType<number> = EnumAsString // I have not found a solution for the error here
    let nn: EnumType<number> = EnumAsNumber
    let Nn: EnumType<EnumAsNumber> = EnumAsNumber
    let ss: EnumType<string> = EnumAsString
    let Ss: EnumType<EnumAsString> = EnumAsString
    
    forEachEnum(EnumAsString, value => {
        let e: EnumAsString = value
        let s: string = value
        let n: number = value // Error
    })
    
    forEachEnum(EnumAsNumber, value => {
        let e: EnumAsNumber = value
        let s: string = value // Error
        let n: number = value
    })
    
    forEachEnum2(EnumAsString, value => {
        let e: EnumAsString = value
        let s: string = value
        let n: number = value // Error
    })
    
    forEachEnum2(EnumAsNumber, value => {
        let e: EnumAsNumber = value
        let s: string = value // Error
        let n: number = value
    })
    
    0 讨论(0)
  • 2020-12-01 14:09

    You can do better than any:

    enum E1 {
        A, B, C
    }
    enum E2 {
        X, Y, Z
    }
    
    function getRandomElementOfEnum(e: { [s: number]: string }): number {
        /* insert working implementation here */
        return undefined;
    }
    
    // OK
    var x: E1 = getRandomElementOfEnum(E1);
    // Error
    var y: E2 = getRandomElementOfEnum(window);
    // Error
    var z: string = getRandomElementOfEnum(E2);
    
    0 讨论(0)
  • 2020-12-01 14:12

    Here is an example that allows passing an enum with a typechecked value of that enum using a generic. It's really a response to a slightly different question here that was marked as a duplicate: Typescript how to pass enum as Parameter

    enum Color {
        blue,
    };
    enum Car {
        cadillac,
    };
    enum Shape {
        square,
    }
    
    type SupportedEnums = typeof Color | typeof Car;
    
    type InvertTypeOf<T> = T extends typeof Color ? Color :
        T extends typeof Car ? Car : never;
    
    function getText<T extends SupportedEnums>(enumValue: InvertTypeOf<T>, typeEnum: T) string | undefined {
      if (typeEnum[enumValue]) {
        return `${enumValue}(${typeEnum[enumValue]})`;
      }
    }
    
    console.log(getText(Car.cadillac, Car)); // 0(cadillac)
    console.log(getText(0, Color)); // 0(red)
    console.log(getText(4, Color)); // undefined
    
    // @ts-expect-error Color is not Car
    console.log(getText(Color.blue, Car));
    
    // @ts-expect-error Car is not a Color
    console.log(getText(Car.toyota, Color));
    
    // @ts-expect-error  Shape is not in SupportedEnums
    console.log(getText(5, Shape));
    
    // @ts-expect-error  Shape is not in SupportedEnums
    console.log(getText(Shape.square, Shape));
    
    0 讨论(0)
提交回复
热议问题