使用对象字面量作为 TypeScript 枚举值

Posted

技术标签:

【中文标题】使用对象字面量作为 TypeScript 枚举值【英文标题】:Use object literal as TypeScript enum values 【发布时间】:2017-05-01 23:02:05 【问题描述】:

我有一个枚举:

export enum PizzaSize 
  SMALL =  0,
  MEDIUM = 1,
  LARGE = 2

但在这里我想使用一些值:例如SMALL 我想说它有0key100value。我努力使用:

export enum PizzaSize 
  SMALL =   key: 0, value: 100 ,
  // ...

但是 TypeScript 不接受这个。我该怎么做?

【问题讨论】:

我认为它还不支持..github.com/Microsoft/TypeScript/issues/1206 自 2016 年以来对此有何变化? 【参考方案1】:

仅限 TypeScript supports numeric or string-based enums,因此您必须使用类模拟对象枚举(这将允许您将其用作函数声明中的类型):

export class PizzaSize 
  static readonly SMALL  = new PizzaSize('SMALL', 'A small pizza');
  static readonly MEDIUM = new PizzaSize('MEDIUM', 'A medium pizza');
  static readonly LARGE  = new PizzaSize('LARGE', 'A large pizza');

  // private to disallow creating other instances of this type
  private constructor(private readonly key: string, public readonly value: any) 
  

  toString() 
    return this.key;
  

那么你可以使用预定义的实例来访问他们的value:

const mediumVal = PizzaSize.MEDIUM.value;

或您可能想在PizzaSize 中定义的任何其他属性/属性类型。

并且由于 toString() 覆盖,您还可以从对象中隐式打印枚举名称/键:

console.log(PizzaSize.MEDIUM);  // prints 'MEDIUM'

【讨论】:

比公认的答案更优雅,但是构造函数应该是私有构造函数(私钥:字符串,公共值:任意),以便能够使用 PizzaSize.MEDIUM.value 或定义一个 getter给它 真@Flohlich。刚刚添加了一个 getter 来禁止更改这样的枚举常量。 @Alexander Mills 没有枚举声明,这就是重点:由于不支持对象枚举,您必须通过类声明来模拟它。 我建议也声明静态项目readonly。否则,他们可以在外部重新分配 (PizzaSize.SMALL = PizzaSize.LARGE; // get a big pizza, but pay small :-))。 如果您使用Ramda/lodash 克隆您的对象,请小心使用此方法,例如R.clone(pizza: PizzaSize.MEDIUM)_.cloneDeep。三重等于和 switch 语句不会像使用 TypeScript enum 那样工作【参考方案2】:

更新:在下方找到@Javarome's answer,更优雅。我建议用他的方式。

如果您需要使用 Type,请尝试添加一些代码。 用法:getPizzSizeSpec(PizzaSize.small).value

enum PizzaSize 
    small,
    medium,
    large

interface PizzaSizeSpec 
    key: number,
    value: number

function getPizzaSizeSpec(pizzaSize: PizzaSize): PizzaSizeSpec 
    switch (pizzaSize) 
        case PizzaSize.small:
            return key:0, value: 25;
        case PizzaSize.medium:
            return key:0, value: 35;
        case PizzaSize.large:
            return key:0, value: 50;
    

【讨论】:

查看我最近的答案,它还具有详尽检查的好处 这个答案与对象文字枚举相去甚远。 ***.com/a/51398471/2103767 是迄今为止最好的 @Javarome【参考方案3】:

从Typescript 3.4 开始,您可以使用keyof typeofconst assertions 的组合来创建可以与枚举具有相同类型安全性的对象,并且仍然可以保存复杂值。

通过创建与const 同名的type,您可以进行与普通枚举相同的详尽检查。

唯一的缺点是你需要复杂对象中的一些键(我在这里使用value)来保存枚举成员的名称(如果有人能找出一个可以在类型安全的方式,我很想看到它!我无法让一个工作)。

export const PizzaSize = 
    small:  value: 'small', key: 0, size: 25 ,
    medium:  value: 'medium', key: 1, size: 35 ,
    large:  value: 'large', key: 2, size: 50 ,
 as const

export type PizzaSize = keyof typeof PizzaSize

// if you remove any of these cases, the function won't compile
// because it can't guarantee that you've returned a string
export function order(p: PizzaSize): string 
    switch (p) 
        case PizzaSize.small.value: return 'just for show'
        case PizzaSize.medium.value: return 'just for show'
        case PizzaSize.large.value: return 'just for show'
    


// you can also just hardcode the strings,
// they'll be type checked
export function order(p: PizzaSize): string 
    switch (p) 
        case 'small': return 'just for show'
        case 'medium': return 'just for show'
        case 'large': return 'just for show'
    

在其他文件中这个可以简单的使用,只需要导入PizzaSize即可。

import  PizzaSize  from './pizza'

console.log(PizzaSize.small.key)

type Order =  size: PizzaSize, person: string 

还要注意即使是通常可变的对象也不能使用as const 语法进行变异。

const Thing = 
    ONE:  one: [1, 2, 3] 
 as const

// this won't compile!! Yay!!
Thing.ONE.one.splice(1, 0, 0)

【讨论】:

+1 const ... as const,这对我来说是一个新的!这种方法还可以,但比 Javarome 的方法更难推理 这似乎有点棘手。而且它不允许您像类那样向“枚举”值添加函数。但这会在类没有的地方进行详尽的检查,所以我担心这只是一种权衡选择。 仅供参考,我的 tsc 3.9.7 正在抱怨 order(PizzaSize.large)Argument of type ' readonly value: "large"; readonly key: 2; readonly size: 50; ' is not assignable to parameter of type '"small" | "medium" | "large"'。所以它的行为不像枚举那样。 是的,这个解决方案并不完全像枚举,它只是并行的,使用 const 对象的键而不是实际的枚举值。通过order(PizzaSize.large.value)order('large'),一切都会编译。这样做就像枚举一样类型安全。 在以类型安全的方式构建枚举对象的值方面,我能想到的最好的办法就是按照我定义的方式转换每个声明。它比我想要的要冗长一些,但似乎可以完成工作。【参考方案4】:

我认为要达到你想要的,这样的事情会起作用

interface PizzaInfo 
  name: string;
  cost_multiplier: number;


enum PizzaSize 
  SMALL,
  MEDIUM,
  LARGE,


const pizzas: Record<PizzaSize, PizzaInfo> = 
  [PizzaSize.SMALL]:  name: "Small", cost_multiplier: 0.7 ,
  [PizzaSize.MEDIUM]:  name: "Medium", cost_multiplier: 1.0 ,
  [PizzaSize.LARGE]:  name: "Large", cost_multiplier: 1.5 ,
;

const order = PizzaSize.SMALL;
console.log(pizzas[order].name);  // "Small"

【讨论】:

如果忘记修改pizzas,给PizzaSize添加值,有什么方法会导致编译失败?【参考方案5】:

Object.freeze 使其只读并防止添加更多属性:

const pizzaSize = Object.freeze(
  small:  key: 0, value: 25 ,
  medium:  key: 1, value: 35 ,
  large:  key: 2, value: 50 
)

【讨论】:

只有第一级。您还应该冻结每个更深层次。【参考方案6】:

您可以使用类型化的 const 来实现:

export const PizzaSize: 
    [key: string]:  key: string, value: string ;
 = 
    SMALL:  key: 'key', value: 'value' 
;

您可以选择将类型信息提取到单独的接口声明中:


interface PizzaSizeEnumInstance 
    key: string;
    value: string;


interface PizzaSizeEnum 
    [key: string]: PizzaSizeEnumInstance;


export const PizzaSize: PizzaSizeEnum = 
    SMALL:  key: 'key', value: 'value' 
;

【讨论】:

以上是关于使用对象字面量作为 TypeScript 枚举值的主要内容,如果未能解决你的问题,请参考以下文章

在 TypeScript 中动态访问 const 对象字面量

TypeScript-基础类型

JavaScript语言精粹

你能声明一个允许 typescript 中的未知属性的对象字面量类型吗?

typescript中的类型别名和字面量类型

函数和闭包