Как объединить два перечисления в TypeScript

Предположим, у меня есть два перечисления, как описано ниже в Typcript, затем как их объединить

enum Mammals {
    Humans,
    Bats,
    Dolphins
}

enum Reptiles {
    Snakes,
    Alligators,
    Lizards
}

export default Mammals & Reptiles // For Illustration purpose, Consider both the Enums have been merged.

Теперь, когда я import exported value в другой файл, я должен иметь доступ к значениям из обоих перечислений.

import animalTypes from "./animalTypes"

animalTypes.Humans //valid

animalTypes.Snakes // valid

Как я могу достичь такой функциональности в Typcript?

Ответ 1

Я не собираюсь предлагать решение для слияния с перечислениями (я не мог найти правильный способ сделать это)

Но если вы хотите, чтобы что-то велось как перечисление по сравнению с тем, как вы его потребляете, вы все равно можете использовать объединенный объект в 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

Тогда вы можете использовать Animals.Snakes или Animals.Dolphins, и оба должны быть правильно напечатаны и работать как перечисление

Ответ 2

Перечисления, интерфейсы и типы - рабочее решение для объединения перечислений

Что сбивает с толку, так это типы против значений.

  • Если вы определите значение (let, const и т.д.), Оно будет иметь значение плюс некоторый вычисляемый, но не названный по отдельности тип.
  • Если вы определите type или interface, он создаст именованный тип, но он не будет ни выводиться, ни учитываться в окончательном JS. Это помогает только при написании вашего приложения.
  • Если вы создаете enum в Typescript, он создает имя статического типа, которое вы можете использовать, плюс реальный объект, выводимый в JS, который вы можете использовать.

Из справочника TS:

Использовать перечисление очень просто: просто получите доступ к любому члену как свойству самого перечисления и объявите типы, используя имя перечисления.

Таким образом, если вы Object.assign() два перечисления, это создаст новое объединенное значение (объект), но не новый именованный тип.

Поскольку это больше не enum, вы теряете преимущество наличия значения и именованного типа, но вы все равно можете создать отдельное имя типа в качестве обходного пути.

К счастью, у вас может быть одинаковое имя для значения и типа, и TS будет импортировать оба, если вы их экспортируете.

// 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 площадка для игр

Ответ 3

Проблемы со слиянием:

  • одинаковые значения => значения перезаписаны
  • одинаковые ключи => ключи перезаписаны

  • ❌ Перечисления с одинаковыми значениями (=> значения перезаписываются)

enum AA1 {
  aKey, // = 0
  bKey // = 1
}
enum BB1 {
  cKey, // = 0
  dKey // = 1
}
  • ❌ Перечисления с одинаковыми ключами (=> ключи перезаписываются)
enum AA2 {
  aKey = 1
}
enum BB2 {
  aKey = 2
}
  • ✅ Хорошо
enum AA3 {
  aKey, // = 0
  bKey // = 1
}
enum BB3 {
  cKey = 2,
  dKey // = 3
}
  • ✅ Тоже хорошо
enum AA4 {
  aKey = 'Hello',
  bKey = 0,
  cKey // = 1
}
enum BB4 {
  dKey = 2,
  eKey = 'Hello',
  fKey = 'World'
}

Примечание: aKey = 'Hello' и eKey = 'Hello' работают, потому что перечисление со строковым значением не имеет этого значения в качестве ключа

// 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' }

Слияние

  • ❌ используя типы объединений
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)
  • ❌ используя типы пересечений
type AABB1 = AA4 & BB4; // = never
type AABB1key = AABB1['aKey']; // = never
  • ✅ используя типы пересечений с typeof
type AABB2 = (typeof AA4) & (typeof BB4); // = typeof AA4 & typeof BB4
type AABB2key = AABB2['aKey']; // = AA4.aKey
  • ✅ используя 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' }
  • Type используя typeof с копией js
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;
// };

Совет: вы можете использовать одно и то же имя для типа и значения

const merged = { ...AA4, ...BB4 };
type merged = typeof merged;

const aValue = merged.aKey;
type aType = merged['aKey'];

Ваш случай

Если вы хотите объединить 2 перечисления, у вас есть ~ 3 варианта:

1. Использование строковых перечислений

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. Использование уникальных номеров

enum Mammals {
  Humans = 0,
  Bats,
  Dolphins
}

enum Reptiles {
  Snakes = 2,
  Alligators,
  Lizards
}

export const Animals = { ...Mammals, ...Reptiles };
export type Animals = typeof Animals;

3. Использование вложенных перечислений

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

Примечание: вы также можете объединить вложенные перечисления со следующим кодом. Будьте осторожны, чтобы НЕ иметь дублированные значения, если вы это сделаете!

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
  }
}

Ответ 4

Перечисление TypeScript не только содержит определяемые вами ключи, но и числовое обратное, например:

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

Теперь, если вы попытаетесь объединить их, например, с назначением Object#assign вы получите две клавиши с одинаковым числовым значением:

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

И я полагаю, это не то, что вы хотите.

Один из способов предотвратить это - это вручную присвоить значения перечислению и убедиться, что они разные:

enum Mammals {
    Humans = 0,
    Bats = 1,
    Dolphins = 2
}

enum Reptiles {
    Snakes = 3,
    Alligators = 4,
    Lizards = 5
}

или менее явным, но в остальном эквивалентным:

enum Mammals {
    Humans,
    Bats,
    Dolphins
}

enum Reptiles {
    Snakes = 3,
    Alligators,
    Lizards
}

Во всяком случае, до тех пор, пока вы убедитесь, что у перечислений, которые вы объединяете, есть разные наборы ключей/значений, вы можете объединить их с назначением Object#assign.

Демонстрация игровой площадки

Ответ 5

Я бы сказал, что правильный способ сделать это - определить новый тип:

enum Mammals {
    Humans = 'Humans',
    Bats = 'Bats',
    Dolphins = 'Dolphins',
}

enum Reptiles {
  Snakes = 'Snakes',
  Alligators = 'Alligators',
  Lizards = 'Lizards',
}

type Animals = Mammals | Reptiles;

Ответ 6

Попробуйте этот пример перечисления ------

Перечисления или перечисления - это новый тип данных, поддерживаемый в 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');