I have an enum like this:
enum Test {
'type1',
'type2',
'type3',
'type4',
'type5'
}
function getType(num: number) {
}
what I want is if num = 1, return type1, if num = 2, return type2, if num >= 5, return type5.
How can I do this in getType ?
CodePudding user response:
This is tricky question. Please see related question and my article.
Runtime representation of enum is bidirectional object.
const Test = {
0: "type1",
1: "type2",
2: "type3",
3: "type4",
4: "type5",
type1: 0,
type2: 1,
type3: 2,
type4: 3,
type5: 4,
}
Please keep this in mind during reading.
COnsider this example:
enum MyEnum {
ONE, // 0
TWO // 1
}
// This utility type reverses object.
// All keys become values an all values become keys
type ReverseObj<T extends Record<string, string | number>> = {
[Prop in keyof T as T[Prop]]: Prop
}
{
type _ = ReverseObj<{ age: 42 }> // { 42: "age" }
}
// Type representation of runtime enum value
// Please keep in mind that enum runtime value is bidirectional
// this is why I have used ReverseObject
type EnumToObj = Pick<
{
[Prop in keyof typeof MyEnum]: `${typeof MyEnum[Prop]}`
}, keyof typeof MyEnum
>
type IsKeyValid<
InitialValue extends number,
> =
`${InitialValue}` extends keyof ReverseObj<EnumToObj>
? InitialValue
: never
{
type _ = IsKeyValid<1> // 1
type __ = IsKeyValid<2> // never
}
function handleEnum<
Index extends number,
>(index: IsKeyValid<Index>): `${Index}` extends keyof ReverseObj<EnumToObj> ? ReverseObj<EnumToObj>[`${Index}`] : never
function handleEnum<
Index extends number,
>(index: IsKeyValid<Index>) {
return MyEnum[index]
}
handleEnum(0) // "ONE"
handleEnum(1) // "TWO"
handleEnum(2) // expected error
handleEnum('ONE') // expected error
handleEnum(0.1) // expected error
handleEnum(NaN) // expected error
handleEnum(Infinity) // expected error
Some explanation you will find in comments. The goal is to make illegal state unrepresentable.
IsKeyValid - assures that you are not allowed to provide keys which are not exists in enum. For instance, you are not allowed to call handleEnum(10) because we have only two keys, hence allowed keys are 0 | 1. As for the return value, I have obtained it from reversed object ReverseObj.
Some thing you should be aware of:
- Types
typeof MyEnumandMyEnumare not equal. - More explanation about using
asin[Prop in keyof T as T[Prop]]: Propyou can find here - It worth using simple tuple instead of enum if you don't use custom initial indexes for enum. COnsider this example:
const TUPLE = ['one', 'two', 'three'] as const;
type Tuple = typeof TUPLE
type AllowedIndex<Index extends number> = `${Index}` extends Exclude<keyof Tuple, keyof ReadonlyArray<any>> ? Index : never
const getter = <Index extends number>(index: AllowedIndex<Index>) =>
TUPLE[index]
getter(1) // ok, returns "two"
getter(23) // expected error
- In most cases there is even better to use immutable object instead of enum.
const FakeEnum = {
a: 0,
b: 1
} as const
It is easy to work with FakeEnum object. Because you can easily, without any tricks obtain keys and appropertiate values of object.
IMHO, I don't think that enum is the best option to use in this case
P.S. If you don't like zero based index in enums, you can set your initial value:
enum MyEnum {
ONE = 1, // 1
TWO // 2
}
UPDATE
Above approach has his own drawbacks. As you might have noticed, all utility types are binded with MyEnum. They are not generic. If you are interested in generic solution where you can pass any enum, consider this example:
enum MyEnum {
ONE, // 0
TWO // 1
}
type EnumType = Record<string | number, string | number>
type Values<T> = T[keyof T]
// This utility type reverses object.
// All keys become values an all values become keys
type ReverseObj<
T extends Record<string, string | number>,
Reversed = {
[Prop in keyof T as T[Prop]]: Prop
}> =
Values<T> extends keyof Reversed
? Pick<Reversed, Values<T>>
: never
{
type _ = ReverseObj<typeof MyEnum> // { 42: "age" }
}
// Type representation of runtime enum value
// Please keep in mind that enum runtime value is bidirectional
// this is why I have used ReverseObject
type EnumToObj<Enum extends EnumType> = Pick<
{
[Prop in keyof Enum]: Enum[Prop] extends string | number ? `${Enum[Prop]}` : never
}, keyof Enum
>
type O = EnumToObj<typeof MyEnum>
type IsKeyValid<
InitialValue extends number,
Enum extends EnumType
> =
`${InitialValue}` extends keyof ReverseObj<EnumToObj<Enum>>
? InitialValue
: never
{
type _ = IsKeyValid<1, typeof MyEnum> // 1
type __ = IsKeyValid<2, typeof MyEnum> // never
}
function handleEnum<
Index extends number,
Enum extends EnumType
>(enEnum: Enum, index: IsKeyValid<Index, Enum>): `${Index}` extends keyof ReverseObj<Enum> ? ReverseObj<Enum>[`${Index}`] : never
function handleEnum<
Index extends number,
Enum extends EnumType
>(enEnum: Enum, index: IsKeyValid<Index, Enum>) {
return enEnum[index]
}
handleEnum(MyEnum, 0) // "ONE"
handleEnum(MyEnum, 1) // "TWO"
handleEnum(MyEnum, 2) // expected error
handleEnum(MyEnum, 'ONE') // expected error
CodePudding user response:
TypeScript enums are accessible though there number index on runtime, so
return Test[1];
Will return index 1 from the Test enum.
Since you want 1 to return type1, we'll need to subtract 1 from your num to get the desired index:
enum Test {
'type1',
'type2',
'type3',
'type4',
'type5'
}
function getType(num: number) {
return Test[num - 1];
}
console.log(getType(1));
Will output:
type1
