array enumerations as runtime array + union type
See original GitHub issueSuggestion
Array enums
const X = enum ['a', 'b', 'c'];
// or
const X = ['a', 'b', 'c'] as enum;
🔍 Search Terms
Related tickets
- https://github.com/microsoft/TypeScript/issues/41122
- https://github.com/microsoft/TypeScript/issues/38960
- https://github.com/microsoft/TypeScript/issues/21391
✅ Viability Checklist
- This wouldn’t be a breaking change in existing TypeScript/JavaScript code
- This wouldn’t change the runtime behavior of existing JavaScript code
- This could be implemented without emitting different JS based on the types of the expressions
- This isn’t a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
- This feature would agree with the rest of TypeScript’s Design Goals.
⭐ Suggestion
The idea is that if enum
keyword is used in a constant array declaration, it leaves the array as runtime value but also returns the type as a union type of the values.
📃 Motivation
Currently enums
are causing lots of issues by their nature that violates a few of the TypeScript design goals:
- Impose no runtime overhead on emitted programs
- Use a consistent, fully erasable, structural type system.
They require devs to create key-value pairs for each entry in the enum and that transpiles to a considerable amount of runtime code.
const enums
came to help there but they lack the ability to get the list of valid values as a runtime array for runtime validation.
This can solve both issues with a terse, clean and removable syntax addition, and facilitates the usage of union types as enumerations.
💻 Use Cases
const Status = enum ['active', 'inactive', 'cancelled'];
// or
const Status = ['active', 'inactive', 'cancelled'] as enum;
Would transpile to…
// just `enum` striped
const Status = ['active', 'inactive', 'cancelled'];
… and be identical to…
const Status = ['active', 'inactive', 'cancelled'] as const;
type Status = typeof Status[number];
That means that we can use Status
both as runtime value:
function validate(status) {
if (!Status.includes(status)) {
throw new Error(`${status} is not a valid status: ${Status.join()}`);
}
}
validate('cancelled') // ok
validate('hello') // Runtime error
And as a type
function set(status: Status) {
// do something
}
set('active') // ok
set('hello') // type check error
Even combined
const isStatus = (x: unknown) : x is Status => Status.includes(x);
if (isStatus(queryParams.status)) {
set(queryParams.status) // type narrowed to Status
}
Abstraction in code
This abstraction reaches the same goal but requires users to declare both the runtime array and the type in separated sentences and declare them with the same name.
type ValidKeys = string | number;
type Enum<T extends ValidKeys[]> = T[number];
function Enum<T extends ValidKeys[]>(...keys: [...T]) {
return keys;
}
const Status = Enum('active', 'inactive', 'cancelled');
type Status = Enum<typeof Status>;
Comparison with enum
& const enum
Declaration
enum Enum { A = 'a' }
const enum ConstEnum { A = 'a' }
const ArrayEnum = ['a'] as enum;
Usage as type
function enumFn(x: Enum) {}
function constEnumFn(x: ConstEnum) {}
function arrayEnumFn(x: ArrayEnum) {}
function EnumJsx(props: {x: Enum}) { return null }
function ConstEnumJsx(props: {x: ConstEnum}) { return null }
function ArrayEnumJsx(props: {x: ArrayEnum}) { return null }
Value usage
// Enum
enumFn(Enum.A);
enumFn('a' as Enum);
<EnumJsx x={Enum.A} />
<EnumJsx x={'a' as Enum} />
// ConstEnum
constEnumFn(ConstEnum.A);
constEnumFn('a' as ConstEnum);
<ConstEnumJsx x={ConstEnum.A} />;
<ConstEnumJsx x={'a' as ConstEnum} />;
// ArrayEnum
arrayEnumFn('a');
<ArrayEnumJsx x="a" />;
Enumerate possible values at runtime
console.log(Object.values(Enum));
// not possible with ConstEnum
console.log(ArrayEnum);
Generated output
// Enum
var Enum;
(function (Enum) {
Enum["A"] = "a";
})(Enum || (Enum = {}));
// ConstEnum
// no output
// ArrayEnum
const ArrayEnum = ['a'];
Alternative
Alternatively we can extend closer to the as const
feature:
Discarded since as const
is expression syntax (see comments)
const Status = ['active', 'inactive', 'cancelled'] as enum;
Issue Analytics
- State:
- Created 2 years ago
- Reactions:3
- Comments:10 (1 by maintainers)
Regardless of the choice of keyword, this is expression-level syntax. What do you propose should happen here:
If you intend that the creation of a new named type is tied specifically to the
const x = ...
then this would be better suited to dedicated syntax that makes that obvious, likeenum type X = 'foo' | 'bar'
, and then it becomes a lot clearer why this feature has runtime impact.Hi, I think the repetitive requests on
enum
is due to the need forRecord<keyof typeof enum, Enum>
. But currently, we haveRecord<keyof typeof enum, Enum> & Record<Enum, keyof typeof enum>
.I am not sure whether `Object.defineProperty´ would violate non-goals, like this
would compile to
But it might not be very compatible with early ES. But we can enable this keyword when and only when the target supports Objcet.defineProperty