自动字符串枚举

时间:2018-06-08 10:43:04

标签: typescript enums

如下所示的字符串枚举看起来非常多余,代码繁琐且容易出错,如果您输错了某些副本(请参阅以下示例中的最后一个)

enum Cmd{
    StartServer = "StartServer",
    StopServer = "StopServer",
    ResumServer1 = "ResumeServer1",
    ResumServer2 = "ResumeServer1"   // this would cause problems
}

我正在寻找一种方法来只声明枚举的元素,并让值自动成为符号名称,并可选择带前缀

我到达的最远的地方是这样的:

export function stringifyEnum(enu:any, prefix:string=undefined){   

  Object.keys(enu).forEach( k =>{
    if (isNumber(k))
        enu[k] = prefix+enu[k]
    else
        enu[k] = prefix+k
  })
}

似乎有效:

enum Cmd{
    StartServer,
    StopServer,
    ResumeServer1,
    ResumeServer2
}

stringifyEnum(Cmd,"Cmd")
console.log(Cmd.StartServer)  // --> CmdStartServer

使用这种方法,到目前为止,唯一的问题是打字稿认为枚举是数字的并且在某些情况下会抱怨。

有没有更好的方法(这里的主要目标是简洁的枚举)或者您是否看到这个想法隐藏的风险?

3 个答案:

答案 0 :(得分:3)

我认为您不能以编程方式执行前缀,以便类型系统理解它;这将需要一些不属于该语言的type operations。此功能有一个existing suggestion,但看起来并没有任何人正在使用它。

至于获取键和值完全相同,您可以编写一个函数,该函数接受一个字符串列表并生成一个强类型的“枚举”类似的映射:

function enumize<K extends string>(...args: K[]): {[P in K]: P} {
  const ret = {} as {[P in K]: P};
  args.forEach(k => ret[k]=k);
  return ret;
}
const Cmd = enumize("StartServer", "StopServer", "ResumeServer1", "ResumeServer2");

我们认识到值Cmd的类型为{ StartServer: "StartServer"; ... }。您将能够按预期访问元素:

console.log(Cmd.StartServer); // works

官方enum还会创建一些名为的类型,而enumize()则不会。要完全复制类型,您需要做更多的工作:

type Cmd = keyof typeof Cmd; // allows you to refer to the type Cmd as before

这是@ RobbyCornelissen的回答中提到的联合类型。如果您要将Cmd称为类型,则需要它,如:

declare function requireCmd(cmd: Cmd); // the Cmd here is a type
requireCmd(Cmd.StopServer); // works

如果你需要引用每个枚举元素的类型,你必须做更多繁忙的工作,包括丑陋的代码重复:

namespace Cmd {
  export type StartServer = typeof Cmd.StartServer
  export type StopServer = typeof Cmd.StopServer
  export type ResumeServer1 = typeof Cmd.ResumeServer1
  export type ResumeServer2 = typeof Cmd.ResumeServer2
}

需要那些东西来引用像Cmd.StopServer这样的类型,如:

interface CommandInfo {
  kind: Cmd;
}

interface StopServerInfo extends CommandInfo {
   kind: Cmd.StopServer;  // need namespace for this line
   commandIssueDate: Date;
   numberOfUsersForcedOffline: number;
}

但是如果你不打算这么做,那么你可以省略namespace的东西......你总是可以使用typeof Cmd.StopServer类型。

希望有所帮助;祝你好运。

答案 1 :(得分:1)

我几乎放弃了TypeScript中的枚举,转而使用string literal union types

对于您的示例,如下所示:

type Cmd = 'StartServer' | 'StopServer' | 'ResumeServer1' | 'ResumeServer2';

这种方法在编译时检查中会给你带来与枚举相同的好处:

function foo(cmd: Cmd) {}

foo('StartServer'); // OK
foo('BeginServer'); // error

答案 2 :(得分:0)

如果您正在寻找一种方法来实现多个键可以映射到相同值的方案,那么地图可以帮助您实现目标。你可以冻结它以确保它是不可变的:

const CmdMap = {
    StartServer: "StartServer",
    StopServer: "StopServer",
    ResumServer1: "ResumeServer1",
    ResumServer2: "ResumeServer1"
};

Object.freeze(CmdMap);

如果您想要有前缀命令,请从工厂获取地图:

function getCommandMap(prefix: string) {
    const cmdMap = {
        StartServer: `${prefix}StartServer`,
        StopServer: `${prefix}StopServer`,
        ResumServer1: `${prefix}ResumeServer1`,
        ResumServer2: `${prefix}ResumeServer1`
    };

    Object.freeze(cmdMap);

    return cmdMap;
}

const CmdMap = getCommandMap('Cmd');

console.log(CmdMap.StartServer);