[HN Gopher] An ode to TypeScript enums
___________________________________________________________________
An ode to TypeScript enums
Author : disintegrator
Score : 49 points
Date : 2025-03-02 17:23 UTC (5 hours ago)
(HTM) web link (blog.disintegrator.dev)
(TXT) w3m dump (blog.disintegrator.dev)
| bubblyworld wrote:
| One thing I find useful about enums is that they can be used as
| both types and values, which is ergonomic for decorator-based
| libraries (like class-validator, nestjs, mikro-orm, etc). The
| best approach I've found in union land is using _const_
| assertions and _typeof_ , which I don't love.
|
| Agree with the author that in almost every other way unions are
| better though... they play much more nicely with the rest of the
| type system. I find it endlessly annoying that I have to refer to
| enum members directly instead of just using literals like you can
| with union types.
| disintegrator wrote:
| > One thing I find useful about enums is that they can be used
| as both types and values
|
| Makes sense. You can emulate that behavior by having an object
| literal with const assertion AND a union type of the same name
| derived from the object literal.
| bubblyworld wrote:
| Right, yeah - this is what I meant by _const_ and _typeof_.
| It 's definitely an option, but I'm nervous of relying on the
| semantics of _const_ like that. But maybe I shouldn 't be, it
| seems pretty idiomatic?
|
| (the _typeof_ part is just so you don 't repeat yourself, or
| did you have something else in mind?)
| wruza wrote:
| Parameter properties also gone? I only recently found out about
| these, so useful for data-ish classes.
| disintegrator wrote:
| In principle you'll still be able to use all of the features
| that existed before this flag but you'll need to compile the
| code if targeting Node.js. I do think that this new flag is
| going to draw people away and we'll probably see a bunch of
| tsconfig presets and boilerplate projects setting it to true.
|
| If you're using a bundler then your're not to going benefit
| from it in the medium term. It's possible this will unlock
| faster build times with them in the future.
| lelandfe wrote:
| > _TypeScript 5.8 is out bringing with it the
| --erasableSyntaxOnly flag_
|
| TypeScript sure loves the "our only documentation lives in the
| changelog" approach to stuff, huh?
|
| - The on-site Algolia search returns 0 results for
| "erasableSyntaxOnly"
|
| - The blocked-from-search release notes[0] looks like actual
| documentation - but urges to check out the PR[1] "for more
| information," despite the PR description being essentially blank.
|
| - The CLI options page[2] describes it thus: "Do not allow
| runtime constructs that are not part of ECMAScript," with no
| links to learn more about what that means.
|
| Edit: actually, I take it back! Clicking the flag on the CLI page
| takes one to an intimidating junk drawer page... but my issues
| with discoverability stand:
| https://www.typescriptlang.org/tsconfig/#erasableSyntaxOnly
|
| [0] https://www.typescriptlang.org/docs/handbook/release-
| notes/t...
|
| [1] https://github.com/microsoft/TypeScript/pull/61011
|
| [2] No idea why on-site search doesn't pick this up:
| https://www.typescriptlang.org/docs/handbook/compiler-option...
| Tadpole9181 wrote:
| It happened within hours, it can take a bit to update the docs
| and then get them reindexed.
| MBCook wrote:
| It was released late last week wasn't it?
| homebrewer wrote:
| const enums are almost never mentioned by these articles for some
| reason. They give you the best of both worlds: they're fully
| erasable, and have good LSP support (do no need to search for
| strings and bump into false matches -- or even worse, for
| numbers).
| furstenheim wrote:
| Lack of LSP support looks really bad in the article proposed
| solution :/.
|
| But const enum seems to have several pitfalls.
| https://www.typescriptlang.org/docs/handbook/enums.html
| krona wrote:
| > const enums are almost never mentioned by these articles for
| some reason.
|
| I think it's because a lot of tooling (excepting TSC) doesn't
| support cross-file const enums. But I agree - it's one of the
| reasons I started using TypeScript way back in 2013. I wouldn't
| be able to write comprehensible performance sensitive code
| without it.
| zdragnar wrote:
| Seeing posts like these, I often feel alone preferring enums to
| string unions.
|
| There are certain situations where refactoring a string in a
| union will not work but refactoring an enum will. I don't want to
| type strings when, semantically, what I want is a discrete type.
| I don't even care that they become strings in JS, because I'm
| using them for the semantic and type benefits, not the benefits
| that come with the string prototype.
| forty wrote:
| That precisely one of my problem with enum: almost all TS type
| is structural typing, why have this exception enums being
| nominal typing?
| Tade0 wrote:
| And to add to the confusion Template Types let you compare
| enums as if they were strings.
| zdragnar wrote:
| Classes aren't interchangable, excepting using a child when a
| parent is called for.
|
| Likewise, enums represent a discrete and unique set. The fact
| that there is either a number or a string used under the hood
| is irrelevant.
|
| I imagine using numbers or strings was useful for interop
| with vanilla JS (where JS needs to call a TS function with an
| enum as an argument), so it makes sense to use it instead of
| Symbols, which is what I typically pretend enumd are.
| bubblyworld wrote:
| Interesting point about semantics. I wish there was a way to
| get the best of both - discrete type (correct semantics) but
| one that is auto inferred from literals in contexts where the
| type system expects it (ergonomics of use). Perhaps there are
| good reasons that doesn't work though, I haven't thought
| through it much =P
| eyelidlessness wrote:
| You're not alone! I've given up the preference on team projects
| for pragmatic reasons, but the semantics of (string) enums are
| still my personal preference.
| forty wrote:
| This is my preferred home made way of doing "Enum" in TS theses
| days
| https://gist.github.com/forty/ac392b0413c711eb2d8c628b3e7698... -
| it includes syntax to migrate from TS enum.
|
| The member documentation point is a good one, I'll look what can
| be done with my solution.
| krona wrote:
| My enums are almost always numbers, not strings.
| forty wrote:
| It should be supported by the above code
| spankalee wrote:
| Const objects really are better than enums, in every way except
| declaration brevity.
|
| They're erasable syntax, so they work in environments that just
| strip types. Their emit is just what you write without the types.
| They can be composed in a type-safe way with standard JS
| operations.
|
| You can still write JS docs for values, deprecated the, mark them
| as internal, etc. type ValueOf<T> = T[keyof T];
| const Foo = { /** * A one digit *
| @deprecated */ one: '1', two: '2',
| three: '3' } as const; type Foo = ValueOf<typeof
| Foo>; const Bar = { blue: 'blue',
| } as const; type Bar = ValueOf<typeof Bar>;
| // You can union enum objects: const FooOrBar = {...Foo,
| ...Bar}; // And get union of their values: type
| FooOrBar = ValueOf<typeof FooOrBar>; const
| doSomething = (foo: Foo) => {} // You can reference
| values just like enums: doSomething(Foo.two);
| // You can also type-safely reference enum values by their
| // key name: doSomething(Foo['two']);
|
| Given the TypeScript team's stance on new non-erasable syntax, I
| have to think this is how they would have gone if they had `as
| const` from the beginning. Ron Buckton of the TS team is
| championing an enum proposal for JS:
| https://github.com/rbuckton/proposal-enum Hopefully that goes
| somewhere and improves the declaration side of thigns too.
| hassleblad23 wrote:
| Its a shame because I like the enum way of declaration a lot
| more.
|
| `const Foo = { Bar: 'bar' } as const` - this just feels a bit
| weird.
| spankalee wrote:
| That's a taste thing. Personally, I like my TypeScript as a
| superset of JS with types, so I dislike all the custom value-
| space syntax.
|
| `const Foo = { Bar: 'bar' }` is how I would write an enum-
| like object in JS, so that's how I want to write it in
| TypeScript, just with added types.
| DidYaWipe wrote:
| Yes, why do we have "const" at the beginning AND end?
| moogly wrote:
| The `as const` at the end will ensure the type of `Foo` is
| not widened to a `string`.
| disintegrator wrote:
| This is spot on but the issue I called out in my post is that
| there's nothing drawing devs to import the mapping. Like
| there's the immediate convenience of passing a string literal
| to a field that's a string union for instance. You've done a
| nice thing in your snippet and named the mapping and
| corresponding type the same but that's also uncommon though I'm
| seeing it more nowadays. So as I see it it's very possible to
| end up with a codebase that inconsistently uses the object
| mapping if that matters to you.
| spankalee wrote:
| I don't know how important it is to bad use of the values
| directly, but that is also possible.
|
| You have to intersect every value with a brand, like:
| type Enum<T> = { [K in keyof T]: T[K] & {__brand:
| never}; } const _Foo = { one:
| '1', two: '2', three: '3' } as
| const; const Foo = _Foo as Enum<typeof _Foo>;
| type Foo = ValueOf<typeof Foo>;
|
| And now, this will work:
| doSomething(Foo.two);
|
| But this will error: doSomething('2');
| cies wrote:
| When I see this it makes me want to run for
| ReasonML/ReScript/Elm/PureScript.
|
| Sum types (without payloads on the instances they are
| effectively enums) should not require a evening filling
| ceremonial dance event to define.
|
| https://reasonml.github.io/
|
| https://rescript-lang.org/
|
| https://elm-lang.org/
|
| https://www.purescript.org/
|
| (any I forgot?)
|
| It's nice that TS is a strict super set of JS... But that's
| about the only reason TS is nice. Apart from that the "being a
| strict super set" hampers TS is a million and one ways.
|
| To me JS is too broken to fix with a strict super set.
| dkersten wrote:
| > (any I forgot?)
|
| Gleam has real sum types and can compile to JS.
|
| https://gleam.run/
| spankalee wrote:
| > It's nice that TS is a strict super set of JS... But that's
| about the only reason TS is nice.
|
| I mean, yes, exactly?? That's TypeScript's entire reason for
| being, and it's no small thing.
|
| I use TypeScript where I would have used plain JavaScript. If
| I have a reasonable choice of an entirely different language
| - ie, I'm not targeting browsers or Node - then I would
| definitely consider that.
|
| I personally haven't seen that _any_ compile-to-JS language
| is worth the interop tax with browsers or the JS ecosystem,
| and I 've built very complex apps on GWT and used to be on
| the Dart team working on JS interop.
| Freedom2 wrote:
| Civet (https://civet.dev) is probably my favorite one if I
| want something a bit fancier than Typescript, purely
| because it shares the same elements that you are as "opt-
| in" as much as you like, at least in my limited experience.
| hombre_fatal wrote:
| On the other hand, pasting the `type ValueOf<T> = T[keyof
| T];` idiom into your TS code so you can use it for your enums
| is a hell of a lot less ceremony than ditching TS for any of
| the languages you listed. Especially when you can still just
| us TS enums if you wish.
|
| And on top of that, each of them has a whole new collection
| of ceremonies you're going to have to learn.
|
| All for what, to avoid `as const`?
| pjmlp wrote:
| That single reason is all that matters, because it maps
| directly to what the platform actually understands, instead
| of adding another layer to debug.
| ninetyninenine wrote:
| TS sum types are actually more powerful thanks to 'as const'
|
| These are dependent types which none of the languages above
| can enable. Meaning the type system can actually read values
| in your code and create types from the code. This is not
| inferring the type, this is very different.
|
| For example: const PossibleStates = ["test",
| "me"] as const type SumTypeFromArray = (typeof
| PossibleStates)[number] let x: SumTypeFromArray =
| "this string triggers a type error as it is neither 'test'
| nor 'me'"
|
| So in TS you can actually loop through possible states while
| in ML style languages you would have to pattern match them
| individually.
| int_19h wrote:
| TS does have sum types, so you can already do something like:
| type Color = "red" | "green";
|
| What GP is doing is some scaffolding on top to make the
| values more discoverable and allow associating arbitrary
| Color-specific metadata with them.
| fixprix wrote:
| TypeScript/ES6 is such a great language with a feature set far
| ahead of other languages in many ways. The lack of enums though
| is a sore spot. I really hope that proposal you mentioned can
| move forward.
|
| Also you can improve your implementation with
| Object.freeze(Foo) and { one: Symbol("1") }
| DanielHB wrote:
| General programming languages theory question, is one supposed to
| iterate over enum entries or is that considered an antipattern? I
| have found myself needing to do that a few times and it always
| felt a bit dirty.
| panstromek wrote:
| I wouldn't say it's an antipattern. E.g. listing all possible
| values of some user input field is a pretty natural use case
| for that.
| williamdclt wrote:
| No that's fine and a reasonable thing to do . In fact, I'd say
| it is one of the main points of enums and one of my biggest
| gripes against Go is the lack of that capability
| floydnoel wrote:
| Swift can't handle it either, I had to write an extension to
| add that ability. Felt weird to me.
| MBCook wrote:
| What do people find works better as a string enum replacement?
| const Thing { one: "one", two: "two",
| three: "three" } as const
|
| Or just type Thing = "one" | "two" | "three"
|
| I've been thinking of getting rid of the simple string enums I
| have but it's not clear to me why one is preferred over the other
| by people.
| braebo wrote:
| If all you need is a union type then the latter is plenty.
|
| If you need the actual strings to iterate over or validate
| against, deriving the value from an const array is helpful:
| const THINGS = ['one', 'two', 'three'] as const type
| Thing = THINGS[number]
| o11c wrote:
| The problem with "just use literal strings/numbers" is that
| that's exactly the _opposite_ of type safe. With them it is
| impossible to specify an argument of type `myenum | number |
| string`, despite that being commonly desired in some form.
|
| When targeting javascript, it seems to me that the obvious
| approach is to use _symbols_ for enums. But symbols have a lot of
| WET.
|
| (of course, typescript's safety is unfixably broken in numerous
| other ways, so why bother?)
| motorest wrote:
| Can anyone explain why enums are somehow bad but literal unions
| are supposed to be good?
|
| I'll be blunt: at the surface level, it looks like literal unions
| are something that only someone with an irrational axe to grind
| against enums would ever suggest as a preferable alternative just
| to not concede that enums are fine.
|
| If the problem lies in the low-level implementation details of
| enums, I cannot see any reason why they shouldn't be implemented
| the same way as literal unions.
|
| So can anyone offer any explanation on why enums should be
| considered bad but literal unions should be good?
| moogly wrote:
| TypeScript enums require codegen, which won't work in a type
| erasure world. This is explained in the article.
| DidYaWipe wrote:
| "Probably my favorite argument in steelmanning enums"
|
| Whatever that's supposed to mean.
| crummy wrote:
| Steelmanning is the opposite of strawmanning.
|
| In other words, it's making the strongest version of an
| argument for the opposing side of the argument. The author
| doesn't like enums but is talking about their best attributes.
| DidYaWipe wrote:
| Thanks for the reply. I read a lot, and I've never
| encountered this term before. Seems like "in defense of"
| would be every bit as good, and universally understood.
| swatcoder wrote:
| It's an internet term of recent origin, from a specific
| community, not a traditional one.
|
| It's good to be familiar with the word, as it comes up in
| adjacent communities like this one, but like with most
| slang, there are indeed clearer ways to say the same thing.
|
| But also, some people don't realize that they've picked up
| a slang term or that people outside their community are
| part of discussions like we have here, so it comes up a
| lot. Now that you've spotted it, you'll likely see it here
| a lot.
|
| (FWIW, I hate it and am grateful that nobody can see me
| roll my eyes when its used. Same for "motte and bailey" and
| other comically pseudo-erudite slang from those folks)
___________________________________________________________________
(page generated 2025-03-02 23:01 UTC)