Open utterances-bot opened 1 year ago
nice article
perhaps you could also cover how to do it in Typescript
this is my preferred way right now
let sizes = ['small', 'medium', 'large'] as const
type Size = typeof sizes[number] // 'small' | 'medium' | 'large
// Type '"mediaum"' is not assignable to type '"small" | "medium" | "large"'. Did you mean '"medium"'?(2820)
const size: Size = 'mediaum'
nice article
Thanks @opensas!
perhaps you could also cover how to do it in Typescript. this is my preferred way right now
Why not use the regular TypeScript enum type?
Why not use the regular TypeScript enum type?
Event TypeScript documentation says that TS enums may be not the best choice.
In modern TypeScript, you may not need an enum when an object with as const could suffice
As I see many developers now are not using TS enums (so am I)
Nice article, by the word. I didn't know about Proxy enums. Thanks!
Nice line-up of techniques!
One other advantage I see with class-based enums is that you can attach value-specific functionality (i.e., define methods that all enum values have but they behave differently for each enum, polymorphism-like).
Moreover, I guess you can control/disallow new value creation in the constructor, right?
Event TypeScript documentation says that TS enums may be not the best choice.
@va-deem Good point. TypeScript recommends objects as const as an alternative to enums:
const ODirection = {
Up: 0,
Down: 1,
Left: 2,
Right: 3,
} as const;
Nice article, by the word. I didn't know about Proxy enums. Thanks!
You're welcome!
Nice line-up of techniques!
@octavian-nita Thanks!
One other advantage I see with class-based enums is that you can attach value-specific functionality (i.e., define methods that all enum values have but they behave differently for each enum, polymorphism-like).
You can customize further the class-based enum.
Moreover, I guess you can control/disallow new value creation in the constructor, right?
Good idea! It's possible to control the number of created enum instances using a static field (e.g. instancesCount
).
@panzerdb I @va-deem comments, it's not so popular nowadays, and I don't like all the code that typescript generates behind it, I still like to think about typescript as a compile time only thing.
I'm sure the generated code should work ok, but just knowing that thing is there unnerves me (jaja!)
let sizes = ['small', 'medium', 'large'] as const
type Size = typeof sizes[number] // 'small' | 'medium' | 'large
// Type '"mediaum"' is not assignable to type '"small" | "medium" | "large"'. Did you mean '"medium"'?(2820)
const size: Size = 'mediaum'
// using enum
enum SizeEnum { small, medium, large }
const size2: SizeEnum = SizeEnum.small
// ---------------------------
// generated code:
"use strict";
let sizes = ['small', 'medium', 'large'];
// Type '"mediaum"' is not assignable to type '"small" | "medium" | "large"'. Did you mean '"medium"'?(2820)
const size = 'mediaum';
// code generated using enum
var SizeEnum;
(function (SizeEnum) {
SizeEnum[SizeEnum["small"] = 0] = "small";
SizeEnum[SizeEnum["medium"] = 1] = "medium";
SizeEnum[SizeEnum["large"] = 2] = "large";
})(SizeEnum || (SizeEnum = {}));
const size2 = SizeEnum.small;
I've used all of these approaches in the past (and then some), but my current favorite is:
enums/Size.js
export const small = 'small';
export const medium = 'medium';
export const large = 'large';
Use the types:
import * as Size from 'enums/Size';
const foo = Size.medium;
I've used all of these approaches in the past (and then some), but my current favorite is:
@pspaulding An original way to create enums! Thanks for sharing.
Now that I think about it, what I find myself doing most often nowadays is just using the enum from the type of the structure where is id being effectively used, if it is being used by more than one structure I might extract it to it's own type, something like this:
export type Card = {
title: string;
size: 'small' | 'medium' | 'large
...
}
And then I might use it like
let size: Card['size'] = 'small'
for example
Now that I think about it, what I find myself doing most often nowadays is just using the enum from the type of the structure where is id being effectively used, if it is being used by more than one structure I might extract it to it's own type, something like this
@opensas In TypeScript, I'd probably go with a union of string literals too.
We used to use the class-based technique all the time back in the days of ActionScript! I've actually really missed being able to use that technique...
One other pro of using a class-based Enum is that you can also use static methods to convert from a primitive data type into the Enum e.g. const size = Sizes.fromString('small')
or const myColor = Colors.fromHex('#FF0000')
One other pro of using a class-based Enum is that you can also use static methods to convert from a primitive data type into the Enum e.g. const size = Sizes.fromString('small') or const myColor = Colors.fromHex('#FF0000')
@moloko That's very true. Thanks for sharing.
Here's a small module to create/use flagged enums.
@pspaulding
// enums/Size.js
export const small = 'small';
export const medium = 'medium';
export const large = 'large';
// Use the types:
import * as Size from 'enums/Size';
I used to do that until I realised that it's not tree shakeable. The object approach is actually the best, cause it's tree shakeable. Seems counter intuitive, I didn't believe it until I experimented with it.
e.g
let user_size = Size.small
will get transpiled to
let user_size = "small"
Bundlers are able to understand how you use a const Size {small: "small", ...} as const
than your stated approach. So they can get rid of the other enum members if you don't use them and inline the ones you do use.
const Size = Enum( 'BIG', 'MID', 'SMALL' )
//console.log(Size): Proxy(Object) {BIG: Symbol(BIG), MID: Symbol(MID), SMALL: Symbol(SMALL)}
Size.BIG !== Size.MID // true
function Enum( ...list ) {
const enumObj = Object.create(null)
list.forEach( el => enumObj[el] = Symbol(el) )
return new Proxy(enumObj, {
get(target, name) {
if (!Object.hasOwn(enumObj, name)) {
throw new Error(`"${name}" value does not exist in the enum`)
}
return enumObj[name]
},
set(target, name, value) {
throw new Error('Cannot add a new value to the enum')
},
deleteProperty(target, name) {
throw new Error('Cannot delete a property for the enum')
}
})
}
Modifying
list.forEach( el => enumObj[el] = Symbol(el) )
to
list.flat().forEach( el => enumObj[el] = Symbol(el) )
in the above enumeration factory we enable it to accept both arguments list and arrays:
const Size = Enum( 'BIG', 'MID', 'SMALL' )
const Size2 = Enum( [ 'BIG', 'MID', 'SMALL' ] )
(yielding the same result: // Proxy(Object) {BIG: Symbol(BIG), MID: Symbol(MID), SMALL: Symbol(SMALL)}
Nice article.
I had been using Object.create()
with a null prototype and property descriptors to make sure the values are read only. By default the properties are not writable or configurable.
const Size = Object.create(null, {
Small: { value: 0 },
Medium: { value: 1},
Large: { value: 2}
};
4 Ways to Create an Enum in JavaScript
An enum type represents a set of constants. The plain object, frozen object, proxied object or class-based are the 4 ways to create enums in JavaScript.
https://dmitripavlutin.com/javascript-enum/