5 ms·
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 sti
by skrebbel 2y ago
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.
- tgv 2y agoChanging 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.
- mistercow 2y ago> 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.
- skrebbel 2y ago> 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!
- mistercow 2y agoI 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.
- gherkinnn 2y agoAnd I bet 6 out of those 9 times it is because enums are nominally typed when the rest of TS is structurally typed.
- mistercow 2y agoI 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.
- WorldMaker 2y agoAt 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.
- deleted 2y ago[deleted]
- leidenfrost 2y agoI wonder if there's a guide of recommendations about typescript now deprecated features, and its modern equivalents.
- msoad 2y agoYeah they should deprecate namespaces and enums in the next major version... oh wait... TypeScript versioning is literally a joke
- cap11235 2y agoIt's js devs, what do you expect
- Dylan16807 2y agoWhat are you implying with "oh wait"? They have major versions every once in a while, and breaking changes. They could do that.
- silverwind 2y agoThey 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.
- Dylan16807 2y agoDoes 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.
- msoad 2y agoI'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
- n144q 2y agoThey 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.
- mistercow 2y ago> Not to mention that you can add documentation to each of the entries. You can do that with either other solution.
- WorldMaker 2y agotype 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.
- n144q 2y agoOf course they work, I use this in JavaScript all the time. But why would I do that when I have real enum in TypeScript?
- baobun 2y agoBecause 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)
- deleted 2y ago[deleted]