【问题标题】:How to type function taking an enum如何键入带枚举的函数
【发布时间】:2020-09-27 18:28:45
【问题描述】:

给定一些enum MyEnum {ONE, TWO},我想写一个叫做like的函数

useMyFun(MyEnum, MyEnum.ONE);

我没有正确输入。现在我有类似以下的内容

type StringKeyOf<T> = Extract<keyof T, string>;
type EnumNumber<E> = Record<StringKeyOf<E>, number>;
function useMyFun<E extends EnumNumber<E>, V extends number=number> (
    anEnum: E,
    initialState: number) : {value: V, setValue: (v: V) => void}
{
    const [value, setValue] = useState<V>(initialState as V);
    //.... more stuff using both arguments omitted    
    return {value, setValue};
}

它是一个 react-hook,但这并不重要,因为你只需要编译它就是一个 dummy

function useState<V>(initialState: V) {
    const result: [V, (v: V) => void] = [initialState, v => { }];
    return result;
}

它可以工作(使用当前的打字稿版本),但它也允许我调用useMyFun(MyEnum, -1),这是错误的。请注意,我只关心上面的枚举,即具有默认数值、没有指定值且没有 const 修饰符的枚举。

*我还需要返回类型为value: MyEnum 而不是number


我知道MyEnum的运行时值是{0: 'ONE', 1: 'TWO', ONE: '0', TWO: '1'},也就是说上面的输入实际上是错误的。然而,这是编译第一个参数的唯一方法。处理MyEnum 时的第二个参数实际上应该是0 | 1,但我无法让它工作。

我真的需要枚举对象和函数中的值。 有人能把类型弄对吗?

【问题讨论】:

  • 你是否试图让函数的第二个参数只允许来自枚举的特定值?
  • 您在使用枚举方面投入了多少?
  • @Jacquesジャック不,我需要在结果中提供所有枚举值以填充选择(上面未显示)。
  • @rob3c 在这里,枚举最好,因为不像'ONE' | 'TWO',它们提供了所有值的列表,这正是我所需要的。
  • @maaartinus 我不同意他们在这里是最好的,因为您已经在问题中遇到了参数值限制的重大限制。但是,您似乎已经预先决定了什么是最好的,所以我将留给您。祝你好运!

标签: typescript enums type-inference typescript-generics


【解决方案1】:

相关问题可以找here

还有另一种方法可以判断初始值是否有效。

如您所知,TS 可能会将enum 视为number 或对象或typeof enum。 typescript 以类似的方式处理classes

我们需要一些如何获取enum的数字键。

让我们尝试遍历enum 键:

enum MyEnum {
    ONE,
    TWO
}

type Enumerate<Enum extends number | string> = keyof {
    [Prop in Enum]: Prop
}

// non generic version 
type Keys = keyof typeof MyEnum
type Enumerate2 = keyof {
    [Prop in Keys]: Prop
}


type Result = Enumerate<MyEnum> // MyEnum, not good

它不起作用,因为 TS 足够聪明,可以找出我们正在迭代枚举键。因此我们得到的是MyEnum 而不是0 | 1

我们可以将Prop 键包装成一个字符串来欺骗打字稿。

enum MyEnum {
    ONE,
    TWO
}

type Enumerate<Enum extends number | string> = keyof {
    [Prop in `${Enum}`]: Prop
}

type Result = Enumerate<MyEnum> // "0" | "1"

现在好多了。但这仍然不是我们想要的。在当前版本的打字稿中,无法以通用方式从字符串中提取数字。

但我们总是可以将字符串与在比较过程中包裹在字符串中的数字进行比较。 我的意思是这样的:"0" extends ${number} ? ...` 以上代码完全有效。

enum MyEnum {
    ONE,
    TWO
}

type Enumerate<Enum extends number | string> = keyof {
    [Prop in `${Enum}`]: Prop
}

type Result = Enumerate<MyEnum> // "0" | "1"


type Values<T> = T[keyof T]

type IsKeyValid<InitialValue extends number, Enum extends Record<string | number, string | number>> =
    `${InitialValue}` extends Enumerate<Values<Enum>> ? InitialValue : never

function useMyFun<
    Enum extends Record<string | number, string | number>,
    InitialValue extends number,
    >(anEnum: Enum, initialState: IsKeyValid<InitialValue, Enum>) { }

useMyFun(MyEnum, MyEnum.ONE) // ok
useMyFun(MyEnum, 0) // ok

useMyFun(MyEnum, -1) // error
useMyFun(MyEnum, NaN) // error

Playground

Enum - 是枚举的推断类型

InitialValue - 是第二个参数的推断类型。

IsKeyValid - 是一种实用程序类型,用于检查包装到字符串 InitialValue 中是否等于允许的枚举键。如果相等 - 返回InitialValue,否则返回never

附:与 React 组件道具相关的 question

【讨论】:

    【解决方案2】:

    你的方式有问题。当Enum有'0'时,它可以将0传递给函数。

    type EnumToNumber<T extends number, arr extends number[] = []>
        = `${T}` extends `-${infer N}` ? never :
        T extends arr['length'] ? arr['length']
        : EnumToNumber<T, [...arr, 1]>
    
    type SafeEnum<T extends Record<string | number, string | number>,
        >
        = {
            [key in keyof T]: T[key] extends number ? EnumToNumber<T[key]> : `${T[key]}`
        }[keyof T]
    

    它可以与字符串枚举一起使用。但是我找不到将负数字符串文字转换为负数文字的方法

    enum E{
      A=0,
      B=1,
      C='0'
    }
    type E=SafeEnum<typeof E> //0,1,'0'
    

    【讨论】:

      【解决方案3】:

      让我先说我强烈建议不要这样做,但为了好玩,这里有一个 hack 来获得你正在寻找的行为。该解决方案有效地将枚举值转换为opaque type

      enum _MyEnum {
        ONE,
        TWO,
      }
      
      declare const __brand: unique symbol;
      
      type MyEnum = {
        [K in keyof typeof _MyEnum]: (typeof _MyEnum)[K] & {
          readonly [__brand]: never;
        };
      };
      
      const MyEnum: MyEnum = _MyEnum as any;
      
      type StringKeyOf<T> = Extract<keyof T, string>;
      type EnumNumber<E> = Record<StringKeyOf<E>, number>;
      
      function useMyFun<E extends EnumNumber<E>, V extends E[keyof E]>(
        anEnum: E,
        initialState: V
      ): { value: V; setValue: (v: V) => void } {
        const [value, setValue] = useState<V>(initialState as V);
        //.... more stuff using both arguments omitted
        MyEnum.ONE.toFixed;
        MyEnum.ONE;
        return { value, setValue };
      }
      
      useMyFun(MyEnum, MyEnum.ONE);
      useMyFun(MyEnum, -1);
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-04-04
        • 1970-01-01
        • 1970-01-01
        • 2020-08-09
        • 2021-01-16
        • 1970-01-01
        • 2020-06-20
        • 1970-01-01
        相关资源
        最近更新 更多