Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

One thing I'm missing in the comments here is that enums are a very early TypeScript feature. They were in there nearly from the start, when the project was still trying to find clarity on its goals and principles.

Since then:

- TypeScript added string literals and unions, eg `type Status = "Active" | "Inactive"`

- TypeScript added `as const`, eg `const Status = { Active: 0, Inactive: 1 } as const`

- TypeScript adopted a stance that features should only generate runtime code when it's on a standards track

Enums made some sense back when TS didn't have any of these. They don't really make a lot of sense now. I think they're effectively deprecated, to the point that I wonder why they don't document them as deprecated.



I think they also haven't gotten very much attention in the last few years as new features have been added. Nine times out of ten, if I hit a weird case where TS doesn't understand some type that it really seems like it should understand, it involves an enum. And if I rewrite the enum as a union type and update the other code that uses it, my issue goes away.

I agree they should just formally deprecate it.


And I bet 6 out of those 9 times it is because enums are nominally typed when the rest of TS is structurally typed.


I think that's part of the underlying issue in every case, but then there sometimes seems to be some kind of bug where TS won't agree that the value actually has that nominal type, despite it originating from the enum itself. I usually then can't reproduce these issues with more minimal examples.


At least some of the time an enum doesn't agree with itself it's an import graph issue where the enum is getting imported from more than one place (perhaps because of multiple versions of a dependency in the middle) and the nominal typing is getting overly conservative that those things despite the same contents may be different things. I have a supposition that this is indirectly because what remains of nominal typing is Symbol types and Symbols do have to be extremely careful about import boundaries, especially when bundlers are involved.


They still make sense in terms of clarity, readability and reusability. I use enum every time there are more than 2 entries -- literal types and "as const" are just ugly in comparison.

Not to mention that you can add documentation to each of the entries.


    type SomeProcessState =
      // when waiting for user interaction
      | 'idle'
      // busy processing
      | 'busy'
      // finished processing
      | 'success'

    const OtherProcessStates = Object.freeze({
      /**
       Waiting for user interaction
      */
      Idle: 0,
      /**
       Busy processing
      */
      Busy: 1,
      /**
       Finished processing
      */
      Success: 2,
    } as const)
    type OtherProcessState = typeof OtherProcessStates[keyof typeof OtherProcessStates]
The second form those are even working JSDOC comments.


I'm not often given to aesthetic pronouncements regarding code, but I have to agree that is rather ugly looking.


Of course they work, I use this in JavaScript all the time. But why would I do that when I have real enum in TypeScript?


> real enum in TypeScript?

I think the point is that enums aren't real in Typescript because enums aren't real in JS. They are fakes that generate a bunch of JS code that you are better off writing by hand (such as examples above and elsewhere, or with libraries like io-ts or Zod or other great options). They are fakes that exist mostly because of a desire for backwards compatibility with the type system of Typescript < 1.0, which also means they are increasingly out of touch with modern types.


> I think the point is that enums aren't real in Typescript because enums aren't real in JS.

You used a lot of words to say syntactic sugar.

People love syntactic sugar. It solves their problems and makes their life easier.


No, I used very few words to try to make it clear that enums in Typescript are syntactic salt. Might look sweet from a distance, but it's doing the wrong thing in multiple ways and if you expect it to taste sweet you are in for a surprise. The code generation is ancient and wrong for modern JS. The types are ancient and wrong for modern TS.

People do love syntactic sugar. That's why it is important to point out why sometimes it is table salt and they should avoid trying to put it on their desserts.


> No, I used very few words to try to make it clear that enums in Typescript are syntactic salt.

They aren't. As others already pointed out, enums greatly simplify some usecases, although they brush the complexity of implementing them under the rug.


The complexity they brush under the rug is dangerous and has rough edge cases that have caused real damage in applications I and others have worked on. I'm trying to warn these others that think enums "greatly simplify" things that they don't really simplify things, especially by how much they can hurt. The modern alternatives are all just as simple, but far more reliable. Believe me or not, it is still a fair warning that enums aren't the syntactic "sugar" that they appear to be, they aren't as sweet as they look, and you may get a "salty" surprise (apologies if that sounds more vulgar than intended due to slang overloading) if you keep using them. You don't have to trust my warning on it (or any of the others in this and nearby threads), but that doesn't make it not worth making the warning and suggesting alternatives.

Yes, I've got eslint errors turned on disallowing any use of enums at this point, because I see it as a code smell/risk, but that doesn't mean I'm going to take away the feature from your codebase, that's up to you and your judgment.


can you name some use-case it actually would greaty simplify as you say?

Because neither TFA nor this thread has yet proposed anything that isnt better served by later introduced (say TS 3~4.x+) language features or even external libraries if you actually do want to use them for their generated runtime library (enums) and not just their type-aspects.

I have only seen them complicate things.

You could use it to make typescript prevent forking and code-duplication within a codebase of engineers you dont trust, I guess. But it doesn't seem like a great use either.

Consider https://news.ycombinator.com/item?id=42772924


> You could use it to make typescript prevent forking and code-duplication within a codebase of engineers you dont trust, I guess. But it doesn't seem like a great use either.

Right, for that you are better off using unique Symbols. That's definitely the modern JS way to nominally type something in a private way that stays inside your API boundary and is very much more useful because it is also enforced at runtime, too, and not just an accident of old type mechanics.


Because they are nominally typed, which causes issues for users.

For example, if you already depend on package foo (depending on package baz@^1.0.1, resolving to 1.0.1) and then add a dependency on package bar (depending on package baz@^1.0.2, resolving to 1.0.3), then the same enums from package baz from the two transitive imports are not compatible, since they are not the exact same instance. So TS won't accept a baz enum returned from foo being passed to a function in bar expecting a baz enum. In this example you could fix it by letting the package manager "dedupe" your lockfile since foo could actually happily also use baz@1.0.3. But if the ranges are incompatible, your best hope is aligning resolutions/overrides. Or fall back to forking and patching packages.

And if you're writing your own library interfacing with baz enums, you have to include the full exact version of the originating package to get the right reference. So if baz also has 200MB of total dependencies, you can't opt out of those if you want to reference that 10-line enum in a function signature. As opposed to interfaces and const-types, which you can just vendor (copy-paste exactly what you want) and TS figures it out. You could break the type out to a subpackage. Not so with enums.

If you want to extend a union type, you can just add your own with the new element and it will still typecheck. You can not with enums. So you resort to encapsulation or ad-hoc conversion-functions, which gets frustrating and messy very quickly.

This is only a concern with enums (and classes, where there is good reason for it as the implementation does matter at runtime in a way that enum primitive values do not). The alternatives don't have this issue - as they are structurally typed, TS will "merge" them at type erasure.

If you are 100% sure that your enums stay private inside your module and are never exposed via references in public APIs, at least you're mitigating much of this. But why paint yourself into that corner? (at the point that readability of comments is a concern I suspect you need to reconsider)


I know it’s a bit of an extra bullshit, but in the situation you’re describing, can’t you just run the value through a type guard to make the two enums essentially interoperable?


You just posted a justification to use enums.


> Not to mention that you can add documentation to each of the entries.

You can do that with either other solution.


I wonder if there's a guide of recommendations about typescript now deprecated features, and its modern equivalents.


Given Typescript has preferred opt-in strictness flags for its recommendations, the two big places that seem to be Typescript's best documentation of "deprecated" features seems to be:

1) verbatimModuleSyntax -- https://www.typescriptlang.org/tsconfig/#verbatimModuleSynta...

2) isolatedModules -- https://www.typescriptlang.org/tsconfig/#isolatedModules

Between the two of those flags, enums and namespaces and a few other things get disabled.

The flag documentation doesn't explain what the modern equivalents are, though. I suppose that's currently left to blog posts like the one linked here.


isolatedModules doesn't disable enums in general, only exported const enums (which are arguably the most useful form of enums).


Yeah they should deprecate namespaces and enums in the next major version... oh wait...

TypeScript versioning is literally a joke


What are you implying with "oh wait"? They have major versions every once in a while, and breaking changes. They could do that.


They are not doing Semver. They are just counting two number, e.g. after 4.9 there is 5.0. Afaik, every version can contain breaking changing.


Does that mean I'm supposed to interpret "... oh wait..." as "it would be very easy for them to deprecate a feature because they have lots of sufficiently major releases"? Because it comes across as implying they wouldn't be able to do it.

I really don't understand what GP's point was.


I'm not trying to be cheeky here. They have literally joked about how TypeScript versions means nothing really. So they can't just announce a new major version and drop enums completely. Maybe with a feature flag this is possible but even then, a fresh tsc --init not supporting enums is not really how TypeScript works


But they do break things and drop things. I don't see why they can't do that. I think they simply don't want to remove it very badly.


It's js devs, what do you expect


Changing values (after a change in an external interface), tracking use and renaming is harder in the first case. In the second case, the code can change the value at runtime.


> Changing values (after a change in an external interface), tracking use and renaming is harder in the first case.

You can rename the elements of a string union with the typescript language server. In VS Code at least, it's just like renaming a variable, and it updates the usages which use the type.

> In the second case, the code can change the value at runtime.

You can always freeze the object if you're worried about that.


> Changing values (after a change in an external interface), tracking use and renaming is harder in the first case.

FWIW in VS Code I can rename a string literal (in the type definition) and it's renamed everywhere. Similarly I can use "Find All References", it just works. Pretty cool!




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: