Settings

Theme

TypeScript Enums vs. Flow Enums

medium.com

7 points by GeZe 4 years ago · 8 comments

Reader

brundolf 4 years ago

Many of these problems (some of which I didn't even know about because I avoid using enums) go away when you use union types instead. The implicit casting is one example, the display-name retrieval is another (assuming you use string literals for variants)

Some of them can worked around by structuring your code in certain ways (for example you could make sure every variant is covered by indexing into an object with `key in MyEnum`, or by returning from each switch case and requiring a particular return type)

But even setting those aside, I got burned so badly the last time I used Flow that I'm not sure anything could convince me to return to it

NoahTheDuke 4 years ago

This is a significant improvement on Typescript’s enums but I wonder what the underlying code it compiles to is.

Because we’re planning on moving our codebase to ESM support (the community is moving over quickly), we’ve run into a number of issues with TS enums, the primary one being that TS enums compile to undefined vars that are populated with an IIFE, which disallows using them in certain import/export situations.

  • GeZeOP 4 years ago

    Here are the docs for exporting/importing enums: https://flow.org/en/docs/enums/using-enums/#toc-exporting-en... - works fine with the new export/import syntax.

    Docs for enums compilation/runtime are here: https://flow.org/en/docs/enums/defining-enums/#toc-enums-at-...

  • brundolf 4 years ago

    My recommendation is to not use enums; use plain string unions instead. There's no wondering about how they compile or serialize (or deserialize), there's no importing/exporting (except the types), you even get autocomplete. And TypeScript makes them every bit as safe as enums (more so, based on some things I learned in this article).

    • NoahTheDuke 4 years ago

      That's what we had to do. We now define our "enums" as a `const` object: `export const Example = { First: "first", Second: "second", ... } as const;` and then define the type immediately following: `export type Example = (typeof Example)[keyof typeof Example];` which lets us change the const object without having to also change the type.

      The only downside so far is that `[Example.First, Example.Second].includes(user.example)` no longer works because the array's type is narrowed to the tuple `("first", "second")` instead of an array of Examples `Example[]` without casting: `([Example.First, Example.Second] as Example[]).includes(user.example)` or worse `[Example.First, Example.Second].includes(user.example as any)`. Because this is an extremely common pattern for us, we've taken to using the lodash function `includes` which is typed differently but performs the exact same check under the hood.

      • brundolf 4 years ago

        That is an annoying thing I've run into as well

        My solution is to make a type guard:

          function isExample(val: string): val is Example {
            return (ALL as string[]).includes(val)
          }
        
        I've also found there's no need to even create an object by the way; you can just create a string array and then use string literals directly in your code. Also, TS is smart enough to still give you intelligent autocomplete.

        (you don't even need the array unless you need to iterate over the items at runtime like above; in other situations, you can often just make a type and nothing else)

        • NoahTheDuke 4 years ago

          Oh that’s very clever. Makes me wish javascript/Typescript had macros so I could define this all in a single call.

Keyboard Shortcuts

j
Next item
k
Previous item
o / Enter
Open selected item
?
Show this help
Esc
Close modal / clear selection