How to type function taking an enum
There is another one approach to figure out whether initial value is valid or not.
As you already know, TS might treat enum
as a number
or as an object or typeof enum
. In the similar way typescript treats classes
.
We need some how to obtain numerical keys of enum
.
Let's try to iterate through enum
keys:
enum MyEnum { ONE, TWO}type Enumerate<Enum extends number | string> = keyof { [Prop in Enum]: Prop}// non generic version type Keys = keyof typeof MyEnumtype Enumerate2 = keyof { [Prop in Keys]: Prop}type Result = Enumerate<MyEnum> // MyEnum, not good
It does not work, because TS is smart enought to figure out that we are iterating over enum keys. Hence we are getting MyEnum
instead of 0 | 1
.
We can wrap Prop
key into a string to cheat typescript.
enum MyEnum { ONE, TWO}type Enumerate<Enum extends number | string> = keyof { [Prop in `${Enum}`]: Prop}type Result = Enumerate<MyEnum> // "0" | "1"
Much better now. But it is still not what we want. It is impossible to extract number from a string in current version of typescript in generic way.
But we always can compare string to number which is wrapped in string during the comparison.I mean something like that: "0" extends
${number} ? ...`Above code is perfectly valid.
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 : neverfunction useMyFun< Enum extends Record<string | number, string | number>, InitialValue extends number, >(anEnum: Enum, initialState: IsKeyValid<InitialValue, Enum>) { }useMyFun(MyEnum, MyEnum.ONE) // okuseMyFun(MyEnum, 0) // okuseMyFun(MyEnum, -1) // erroruseMyFun(MyEnum, NaN) // error
Enum
- is infered type of enum
InitialValue
- is infered type of second argument.
IsKeyValid
- is an utility type which checks whether wrapped into string InitialValue
is equal to allowed enum keys or not. If it equal - return InitialValue
, otherwise return never
P.S. Related question with React component props
Let me preface this by saying that I highly advise against doing this, but for the sake of fun, here's a hack to get the behavior you're looking for. This solution effectively makes the enum's values into an 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);
your way have an question.When Enum has '0',it can pass 0 to the function.
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]
it can work with string enum.but i don't find a way to transform negative number string literal to negative number literal
enum E{ A=0, B=1, C='0'}type E=SafeEnum<typeof E> //0,1,'0'