How to merge two enums in TypeScript

前端 未结 6 1643
無奈伤痛
無奈伤痛 2021-01-01 10:30

Suppose I have two enums as described below in Typescript, then How do I merge them

enum Mammals {
    Humans,
    Bats,
    Dolphins
}

enum Reptiles {
             


        
相关标签:
6条回答
  • 2021-01-01 11:07

    Problems with the merge:

    • same values => values are overwritten
    • same keys => keys are overwritten

    • ❌ Enums with same values (=> values are overwritten)

    enum AA1 {
      aKey, // = 0
      bKey // = 1
    }
    enum BB1 {
      cKey, // = 0
      dKey // = 1
    }
    
    • ❌ Enums with the same keys (=> keys are overwritten)
    enum AA2 {
      aKey = 1
    }
    enum BB2 {
      aKey = 2
    }
    
    • ✅ Good
    enum AA3 {
      aKey, // = 0
      bKey // = 1
    }
    enum BB3 {
      cKey = 2,
      dKey // = 3
    }
    
    • ✅ Also Good
    enum AA4 {
      aKey = 'Hello',
      bKey = 0,
      cKey // = 1
    }
    enum BB4 {
      dKey = 2,
      eKey = 'Hello',
      fKey = 'World'
    }
    

    Note: aKey = 'Hello' and eKey = 'Hello' work because the enum with a string value doesn't has this value as key

    // For aKey = 'Hello', key is working
    type aa4aKey = AA4.aKey; // = AA4.aKey
    // value is not.
    type aa4aValue = AA4.Hello; // ❌ Namespace 'AA4' has no exported member 'Hello'
    type aa4aValue2 = AA4['Hello']; // ❌ Property 'Hello' does not exist on type 'AA4'
    
    console.log(AA4); // { 0: 'bKey', 1: 'cKey', aKey: 'Hello', bKey: 0, cKey: 1 }
    console.log(BB4); // { 2: 'dKey', dKey: 2, eKey: 'Hello', fKey: 'World' }
    

    The merge

    • ❌ using union types
    type AABB1 = AA4 | BB4; // = AA4 | BB4
    type AABB1key = AABB1['aKey']; // = never
    type AABB1key2 = AABB1.aKey; // ❌ 'AABB1' only refers to a type, but is being used as a namespace here. ts(2702)
    
    • ❌ using intersection types
    type AABB1 = AA4 & BB4; // = never
    type AABB1key = AABB1['aKey']; // = never
    
    • ✅ using intersection types with typeof
    type AABB2 = (typeof AA4) & (typeof BB4); // = typeof AA4 & typeof BB4
    type AABB2key = AABB2['aKey']; // = AA4.aKey
    
    • ✅ using js copy
    const aabb1 = { ...AA4, ...BB4 };
    const aabb2 = Object.assign({}, AA4, BB4); // also work
    // aabb1 = {
    // 0: 'bKey',
    // 1: 'cKey',
    // 2: 'dKey',
    // aKey: 'Hello',
    // bKey: 0,
    // cKey: 1,
    // dKey: 2,
    // eKey: 'Hello',
    // fKey: 'World' }
    
    • ✅ using typeof with a js copy
    const aabb = { ...AA4, ...BB4 };
    type TypeofAABB = typeof aabb;
    // type TypeofAABB = {
    // [x: number]: string;
    // dKey: BB4.dKey;
    // eKey: BB4.eKey;
    // fKey: BB4.fKey;
    // aKey: AA4.aKey;
    // bKey: AA4.bKey;
    // cKey: AA4.cKey;
    // };
    

    Tip: you can use the same name for a type and a value

    const merged = { ...AA4, ...BB4 };
    type merged = typeof merged;
    
    const aValue = merged.aKey;
    type aType = merged['aKey'];
    

    Your case

    If you want to merge your 2 enums you have ~3 choices:

    1. Using string enums

    enum Mammals {
      Humans = 'Humans',
      Bats = 'Bats',
      Dolphins = 'Dolphins'
    }
    
    enum Reptiles {
      Snakes = 'Snakes',
      Alligators = 'Alligators',
      Lizards = 'Lizards'
    }
    
    export const Animals = { ...Mammals, ...Reptiles };
    export type Animals = typeof Animals;
    

    2. Using unique numbers

    enum Mammals {
      Humans = 0,
      Bats,
      Dolphins
    }
    
    enum Reptiles {
      Snakes = 2,
      Alligators,
      Lizards
    }
    
    export const Animals = { ...Mammals, ...Reptiles };
    export type Animals = typeof Animals;
    

    3. Using nested enums

    enum Mammals {
      Humans,
      Bats,
      Dolphins
    }
    
    enum Reptiles {
      Snakes,
      Alligators,
      Lizards
    }
    
    export const Animals = { Mammals, Reptiles };
    export type Animals = typeof Animals;
    
    const bats = Animals.Mammals.Bats; // = 1
    const alligators = Animals.Reptiles.Alligators; // = 1
    

    Note: you can also merge the nested enums with the following code. Take care to NOT have duplicated values if you do that!

    type Animal = {
      [K in keyof Animals]: {
        [K2 in keyof Animals[K]]: Animals[K][K2]
      }[keyof Animals[K]]
    }[keyof Animals];
    
    const animal: Animal = 0 as any;
    
    switch (animal) {
      case Animals.Mammals.Bats:
      case Animals.Mammals.Dolphins:
      case Animals.Mammals.Humans:
      case Animals.Reptiles.Alligators:
      case Animals.Reptiles.Lizards:
      case Animals.Reptiles.Snakes:
        break;
      default: {
        const invalid: never = animal; // no error
      }
    }
    
    0 讨论(0)
  • 2021-01-01 11:09

    I'd say the proper way to do it would be defining a new type:

    enum Mammals {
        Humans = 'Humans',
        Bats = 'Bats',
        Dolphins = 'Dolphins',
    }
    
    enum Reptiles {
      Snakes = 'Snakes',
      Alligators = 'Alligators',
      Lizards = 'Lizards',
    }
    
    type Animals = Mammals | Reptiles;
    
    0 讨论(0)
  • 2021-01-01 11:14

    A TypeScript enum not only contains the keys you define but also the numerical inverse, so for example:

    Mammals.Humans === 0 && Mammals[0] === 'Humans'
    

    Now, if you try to merge them -- for example with Object#assign -- you'd end up with two keys having the same numerical value:

    const AnimalTypes = Object.assign({}, Mammals, Reptiles);
    console.log(AnimalTypes.Humans === AnimalTypes.Snakes) // true
    

    And I suppose that's not what you want.

    One way to prevent this, is to manually assign the values to the enum and make sure that they are different:

    enum Mammals {
        Humans = 0,
        Bats = 1,
        Dolphins = 2
    }
    
    enum Reptiles {
        Snakes = 3,
        Alligators = 4,
        Lizards = 5
    }
    

    or less explicit but otherwise equivalent:

    enum Mammals {
        Humans,
        Bats,
        Dolphins
    }
    
    enum Reptiles {
        Snakes = 3,
        Alligators,
        Lizards
    }
    

    Anyway, as long as you make sure that the enums you merge have different key/value sets you can merge them with Object#assign.

    Playground Demo

    0 讨论(0)
  • 2021-01-01 11:17

    Try this enumerations example ------

    Enums or enumerations are a new data type supported in TypeScript

    enum PrintMedia {
        Newspaper = 1,
        Newsletter,
        Magazine,
        Book
    }
    
    function getMedia(mediaName: string): PrintMedia {
        if (  mediaName === 'Forbes' || mediaName === 'Outlook') {
            return PrintMedia.Magazine;
        }
     }
    
    let mediaType: PrintMedia = getMedia('Forbes');
    
    0 讨论(0)
  • 2021-01-01 11:29

    I'm not going to propose a solution to merge to enums (I couldn't find a proper way to do it)

    But if you want something behaving like an enum from the way you consume it, you could still use merged object in javascript.

    enum Mammals {
        Humans = 'Humans',
        Bats = 'Bats',
        Dolphins = 'Dolphins',
    }
    
    enum Reptiles {
      Snakes = 'Snakes',
      Alligators = 'Alligators',
      Lizards = 'Lizards',
    }
    
    const Animals = {
       ...Mammals,
       ...Reptiles,
    }
    
    type Animals = Mammals | Reptiles
    

    Then you could use Animals.Snakes or Animals.Dolphins and both should be properly typed and work as an enum

    0 讨论(0)
  • 2021-01-01 11:32

    Enums, interfaces and types - a working solution for merging enums

    What's confusing here is types vs. values.

    • If you define a value (let, const etc.) it will have a value plus some computed but not separately named type.
    • If you define a type or interface, it will create a named type but that will not be outputted or considered in the final JS in any way. It only helps when writing your app.
    • If you create an enum in Typescript, it creates a static type name that you can use plus a real object outputted to JS that you can use.

    From the TS handbook:

    Using an enum is simple: just access any member as a property off of the enum itself, and declare types using the name of the enum.

    So, if you Object.assign() two enums, it will create a new, merged value (object), but not a new named type.

    Since it's not an enum anymore, you lose the advantage of having a value and a named type, but you can still create a separate type name as a workaround.

    Fortunately, you can have the same name for the value and the type, and TS will import both if you export them.

    // This creates a merged enum, but not a type
    const Animals = Object.assign({}, Mammals, Reptiles);
    
    // Workaround: create a named type (typeof Animals won't work here!)
    type Animals = Mammals | Reptiles;
    

    TS playground link

    0 讨论(0)
提交回复
热议问题