如何在 TypeScript 中合并两个枚举

Posted

技术标签:

【中文标题】如何在 TypeScript 中合并两个枚举【英文标题】:How to merge two enums in TypeScript 【发布时间】:2018-07-06 19:07:29 【问题描述】:

假设我有两个在 Typescript 中描述的枚举,那么我该如何合并它们

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

如何在 TypeScript 中实现这样的功能?

【问题讨论】:

【参考方案1】:

您需要对枚举值和正确的导出类型使用字符串 id:

enum Mammals 
  Humans = 'humans',
  Bats = 'bats',
  Dolphins = 'dolphins',


enum Reptiles 
  Snakes = 'snakes',
  Alligators = 'alligators',
  Lizards = 'lizards',


export const Animals =  ...Mammals, ...Reptiles ;
type TAnimalsKeys = keyof typeof Animals;
export type TAnimal = typeof Animals[TAnimalsKeys];

【讨论】:

【参考方案2】:

试试这个枚举示例 ------

枚举或枚举是 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');

【讨论】:

【参考方案3】:

如果你想从消费方式上表现得像枚举,你仍然可以在 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 并且两者都应该被正确键入并作为枚举工作

【讨论】:

没想到这个角度……会试试看。 从@Porkishka 答案中添加type Animals = Mammals | Reptiles;,它可以工作并通过类型检查 我现在要如何导出Animals 如果您同时导出变量和类型,您应该没有任何问题【参考方案4】:

标记一个(也许更好的)方法来做到这一点:

export enum Fruit 
  COCONUT = "COCO",
  BANANA = "BANANA",


export enum Vegetable 
  BROCCOLI = "BROCCOLI",


export const Foods = 
  ...Fruit,
  ...Vegetable,
;

export type Food = keyof typeof Foods;

确保您定义枚举的字符串不会发生冲突。 Food 是类型,Foods 是定义枚举的底层映射(通常 js 会为您创建)。这种方式很酷的地方在于:

Foods[Food.BROCCOLI] 是字符串“BROCCOLI”,就像Fruit[Fruit.COCONUT] 是字符串“COCO”一样,编译器知道这些类型。

因此,结合 FoodsFood 您将获得标准枚举行为。

【讨论】:

【参考方案5】:

一个非常简单的解决方案,copied from here

对于两组不同的枚举:

enum WeatherType 
  CLOUDY,
  SUNNY,
  RAIN

enum WeatherType 
  CLOUDY = 0,
  SUNNY = 1,
  RAIN = 2

然后您可以使用: type MyMergedEnum = AnEnum & AnotherEnum;

【讨论】:

【参考方案6】:

合并问题:

相同的值 => 值被覆盖

相同的键 => 键被覆盖

❌ 具有相同值的枚举(=> 值被覆盖)

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复制
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' 
✅ 将 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
  

【讨论】:

多么全面的答案! 我不能使用带有开关的嵌套枚举 @lolelo 您可以使用打字稿以一种有点骇人听闻的方式做到这一点。这是一个示例(也在编辑的答案中):pastiebin.com/5d681d2cde9cc @ShashankVivek 基本上是复制/粘贴表情符号(符号部分)。它们是书面解释的绝佳资源:) 例如 1 和 2,而不是 export type Animals = typeof Animals; 你应该做 export type Animals = Mammals | Reptiles;所以哺乳动物可以分配给动物【参考方案7】:

枚举、接口和类型 - 合并枚举的有效解决方案

这里令人困惑的是类型与值。

如果您定义一个letconst 等),它将有一个值加上一些计算但未单独命名的类型。 如果你定义了一个typeinterface,它会创建一个命名类型,但不会以任何方式在最终的JS中输出或考虑。只有在编写应用时才有帮助。 如果你在 Typescript 中创建一个enum,它会创建一个你可以使用的静态类型名称​​加上一个输出到 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 playground link

【讨论】:

【参考方案8】:

我认为正确的做法是定义一个新类型:

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


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


type Animals = Mammals | Reptiles;

【讨论】:

这种方法的问题是,您不能从中选择任何值。错误:Animals 然后。 Animals only refers to a type, but is being used as a value here【参考方案9】:

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 合并。

Playground Demo

【讨论】:

合并似乎有效,但我不能在界面中使用它。下面的游乐场演示。 @陶 TS Playground Demo 我想问题是合并的对象不再是 TypeScript 的枚举,尽管它的行为相同。作为一种解决方法,您可以编写typeof AnimalTypes.Bats,它将解析为Mammals.Bats。也许其他人知道更好的方法。

以上是关于如何在 TypeScript 中合并两个枚举的主要内容,如果未能解决你的问题,请参考以下文章

LayaBox---TypeScript---声明合并

如何在 Typescript 中使用动态枚举器(上限量词)克服枚举类型上的 TS2589

TypeScript声明合并

如何使用 TypeScript 枚举值作为类型?

如何使用 TypeScript 键入枚举值?

如何在数据数组和对象的合并中使用对象分配而不是扩展语法?