Typescript has unions, so are enums redundant?

后端 未结 5 1766
闹比i
闹比i 2020-12-01 00:22

Ever since TypeScript introduced unions types, I wonder if there is any reason to declare an enum type. Consider the following enum type declaration:

enum X          


        
5条回答
  •  悲&欢浪女
    2020-12-01 01:25

    Enums can be seen conceptually as a subset of union types, dedicated to int and/or string values, with a few additional features mentioned in other responses that make them friendly to use, e.g. namespace.

    Regarding type safety, numeric enums are the less safe, then come union types and finally string enums:

    // Numeric enum
    enum Colors { Red, Green, Blue }
    const c: Colors = 100; // ⚠️ No errors!
    
    // Equivalent union types
    type Color =
        | 0 | 'Red'
        | 1 | 'Green'
        | 2 | 'Blue';
    
    let color: Color = 'Red'; // ✔️ No error because namespace free
    color = 100; // ✔️ Error: Type '100' is not assignable to type 'Color'
    
    type AltColor = 'Red' | 'Yellow' | 'Blue';
    
    let altColor: AltColor = 'Red';
    color = altColor; // ⚠️ No error because `altColor` type is here narrowed to `"Red"`
    
    // String enum
    enum NamedColors {
      Red   = 'Red',
      Green = 'Green',
      Blue  = 'Blue',
    }
    
    let namedColor: NamedColors = 'Red'; // ✔️ Error: Type '"Red"' is not assignable to type 'Colors'.
    
    enum AltNamedColors {
      Red    = 'Red',
      Yellow = 'Yellow',
      Blue   = 'Blue',
    }
    namedColor = AltNamedColors.Red; // ✔️ Error: Type 'AltNamedColors.Red' is not assignable to type 'Colors'.
    

    More on that topic in this 2ality article: TypeScript enums: How do they work? What can they be used for?


    Union types support heterogenous data and structures, enabling polymorphism for instance:

    class RGB {
        constructor(
            readonly r: number,
            readonly g: number,
            readonly b: number) { }
    
        toHSL() {
            return new HSL(0, 0, 0); // Fake formula
        }
    }
    
    class HSL {
        constructor(
            readonly h: number,
            readonly s: number,
            readonly l: number) { }
    
        lighten() {
            return new HSL(this.h, this.s, this.l + 10);
        }
    }
    
    function lightenColor(c: RGB | HSL) {
        return (c instanceof RGB ? c.toHSL() : c).lighten();
    }
    

    In between enums and union types, singletons can replace enums. It's more verbose but also more object-oriented:

    class Color {
        static readonly Red   = new Color(1, 'Red',   '#FF0000');
        static readonly Green = new Color(2, 'Green', '#00FF00');
        static readonly Blue  = new Color(3, 'Blue',  '#0000FF');
    
        static readonly All: readonly Color[] = [
            Color.Red,
            Color.Green,
            Color.Blue,
        ];
    
        private constructor(
            readonly id: number,
            readonly label: string,
            readonly hex: string) { }
    }
    
    const c = Color.Red;
    
    const colorIds = Color.All.map(x => x.id);
    

    I tend to look at F# to see good modeling practices. A quote from an article on F# enums on F# for fun and profit that can be useful here:

    In general, you should prefer discriminated union types over enums, unless you really need to have an int (or a string) value associated with them

    There are other alternatives to model enums. Some of them are well described in this other 2ality article Alternatives to enums in TypeScript.

提交回复
热议问题