TypeScript War或Enum Conquest

背景知识


半年前,我们公司决定改用更新,更时尚的技术。 为此,组建了一个专家小组,他们将:确定技术堆栈,在此堆栈的基础上架起传统代码的桥梁,最后将一些旧模块转移到新的导轨上。 我很幸运能加入这个小组。 客户代码库大约是一百万行代码。 我们选择TypeScript作为语言。 他们决定结合vue-class-componentIoC在vue上制作GUI基板。

但是这个故事不是关于我们如何摆脱代码遗留的问题,而是关于导致一场真正的知识战争的一个小事件。 谁在乎,欢迎来猫。

了解问题


在开始几个月后,工作组对新堆栈感到满意,并设法将部分旧代码转移到了新堆栈上。 您甚至可以说,当您需要停下来,屏住呼吸并观察我们的所作所为时,有一些关键的肉。

如您所知,需要深入研究的地方就足够了。 但具有讽刺意味的是,在所有重要的事情中,没有任何东西吸引我。 不作为开发人员。 相反,不重要的问题之一就是困扰。 我为我们如何处理枚举数据感到疯狂。 没有概括。 您将遇到带有一组必需方法的单独的类,然后您将找到两个相同的类,甚至是神秘而神奇的类。 没有人要责备。 我们为摆脱遗产而采取的步伐太快了。

在同事之间提出转移的话题之后,我得到了支持。 事实证明,不仅我不满意缺乏与他们合作的统一方法。 在那一刻,在我看来,只需几个小时的编码,我就能达到预期的结果,并自愿纠正这种情况。 但是我当时有多错...

//    ,        . import {Enum} from "ts-jenum"; @Enum("text") export class State { static readonly NEW = new State("New"); static readonly ACTIVE = new State("Active"); static readonly BLOCKED = new State("Blocked"); private constructor(public text: string) { super(); } } //   console.log("" + State.ACTIVE); // Active console.log("" + State.BLOCKED); // Blocked console.log(State.values()); // [State.NEW, State.ACTIVE, State.BLOCKED] console.log(State.valueOf("New")); // State.NEW console.log(State.valueByName("NEW")); // State.NEW console.log(State.ACTIVE.enumName); // ACTIVE 

1.装饰器


从哪里开始? 我想到的只有一件事:以类似Java的枚举为基础。 但是由于我想向同事炫耀,所以我决定放弃传统继承。 请改用装饰器。 另外,装饰器可以使用参数,以便轻松自然地为枚举提供所需的功能。 编码并不需要很多时间,几个小时后,我已经有了类似的东西:

装饰器
 export function Enum(idProperty?: string) { // tslint:disable-next-line return function <T extends Function, V>(target: T): T { if ((target as any).__enumMap__ || (target as any).__enumValues__) { const enumName = (target as any).prototype.constructor.name; throw new Error(`The enumeration ${enumName} has already initialized`); } const enumMap: any = {}; const enumMapByName: any = {}; const enumValues = []; // Lookup static fields for (const key of Object.keys(target)) { const value: any = (target as any)[key]; // Check static field: to be instance of enum type if (value instanceof target) { let id; if (idProperty) { id = (value as any)[idProperty]; if (typeof id !== "string" && typeof id !== "number") { const enumName = (target as any).prototype.constructor.name; throw new Error(`The value of the ${idProperty} property in the enumeration element ${enumName}. ${key} is not a string or a number: ${id}`); } } else { id = key; } if (enumMap[id]) { const enumName = (target as any).prototype.constructor.name; throw new Error(`An element with the identifier ${id}: ${enumName}.${enumMap[id].enumName} already exists in the enumeration ${enumName}`); } enumMap[id] = value; enumMapByName[key] = value; enumValues.push(value); Object.defineProperty(value, "__enumName__", {value: key}); Object.freeze(value); } } Object.freeze(enumMap); Object.freeze(enumValues); Object.defineProperty(target, "__enumMap__", {value: enumMap}); Object.defineProperty(target, "__enumMapByName__", {value: enumMapByName}); Object.defineProperty(target, "__enumValues__", {value: enumValues}); if (idProperty) { Object.defineProperty(target, "__idPropertyName__", {value: idProperty}); } //  values(), valueOf     ,    -. Object.freeze(target); return target; }; } 

在这里,我遭受了第一次失败。 原来,您无法在装饰器的帮助下更改类型。 Microsoft甚至对此主题具有吸引力: Class Decorator Mutation 。 当我说不能更改类型时,是指您的IDE对此一无所知,也不会提供任何提示和足够的自动完成功能。 您可以随心所欲地更改类型,只有它的优点...

2.继承


由于我没有试图说服自己,但我不得不回到基于普通班级创建转学的想法。 是的,那怎么了? 我很生气。 时间不多了,小组中的人,天哪,我要花时间在装饰上。 通常可以在一小时内提交枚举并继续进行。 那就这样吧。 迅速抛出基类的代码可枚举,叹了口气,感到轻松。 我将草稿扔到了通用存储库中,并请我的同事检查解决方案。

可数
 // :     ,  -     export class Enumerable<T> { constructor() { const clazz = this.constructor as any as EnumStore; if (clazz.__enumMap__ || clazz.__enumValues__ || clazz.__enumMapByName__) { throw new Error(`It is forbidden to create ${clazz.name} enumeration elements outside the enumeration`); } } static values<T>(): ReadonlyArray<T> { const clazz = this as any as EnumStore; if (!clazz.__enumValues__) { throw new Error(`${clazz.name} enumeration has not been initialized. It is necessary to add the decorator @Enum to the class`); } return clazz.__enumValues__; } static valueOf<T>(id: string | number): T { const clazz = this as any as EnumStore; if (!clazz.__enumMap__) { throw new Error(`${clazz.name} enumeration has not been initialized. It is necessary to add the decorator @Enum to the class`); } const value = clazz.__enumMap__[id]; if (!value) { throw new Error(`The element with ${id} identifier does not exist in the $ {clazz.name} enumeration`); } return value; } static valueByName<T>(name: string): T { const clazz = this as any as EnumStore; if (!clazz.__enumMapByName__) { throw new Error(`${clazz.name} enumeration has not been initialized. It is necessary to add the decorator @Enum to the class`); } const value = clazz.__enumMapByName__[name]; if (!value) { throw new Error(`The element with ${name} name does not exist in the ${clazz.name} enumeration`); } return value; } get enumName(): string { return (this as any).__enumName__; } toString(): string { const clazz = this.constructor as any as EnumStore; if (clazz.__idPropertyName__) { const self = this as any; return self[clazz.__idPropertyName__]; } return this.enumName; } } 

但是悲喜剧正在全力以赴。 我的计算机上安装了TypeScript版本2.6.2,该版本中存在一个非常宝贵的错误。 无价之宝,因为它不是错误,而是故障。 隔壁房间的声音大声说他什么也不会做。 错误编译( 编译 )。 我不敢相信自己的耳朵,因为即使是草稿,我也总是在推销之前就制定了一个项目。 内心的声音低语:这是惨败,兄弟。

经过短暂的试用,我意识到它是TypeScript版本。 事实证明,如果类的泛型名称与静态方法中指定的泛型名称一致,则编译器会将其视为一种类型。 但是不管是什么,现在对于TypeScript的了解,它已经成为那场战争的历史的一部分。

最重要的是:转移过去和现在存在的问题。 我的悲伤...

注意: 我现在无法使用2.6.2重现此行为,可能是我在版本上犯了错误,或者未在测试用例中添加任何内容。 并且针对上述问题的允许静态成员引用类类型参数的请求被拒绝。

3.投射功能


尽管存在一个弯曲的解决方案,并在静态方法中明确指出了枚举类的类型,例如State.valueOf <State>(),但它并不适合任何人,首先是我。 有一阵子,我什至搁置了该死的转账,并且对我通常可以解决这个问题失去信心。

恢复了我的道德实力后,我在Internet上搜索TypeScript技巧,查看谁遭受了什么痛苦,再次阅读语言文档,以防万一,并决定为避免这种情况完成这项工作。 经过七个小时的连续实验,结果什么也没有发现,甚至没有喝咖啡。 只有一个由一行代码组成的函数将所有内容都放在了适当的位置。

 export function EnumType<T>(): IStaticEnum<T> { return (<IStaticEnum<T>> Enumerable); } //  IStaticEnum : export interface IStaticEnum<T> { new(): {enumName: string}; values(): ReadonlyArray<T>; valueOf(id: string | number): T; valueByName(name: string): T; } 

现在,类似于Java的枚举的声明如下所示:

 import {Enum, EnumType, IStaticEnum} from "ts-jenum"; @Enum("text") export class State extends EnumType<State>() { static readonly NEW = new State("New"); static readonly ACTIVE = new State("Active"); static readonly BLOCKED = new State("Blocked"); private constructor(public text: string) { super(); } } //   console.log("" + State.ACTIVE); // Active console.log("" + State.BLOCKED); // Blocked console.log(State.values()); // [State.NEW, State.ACTIVE, State.BLOCKED] console.log(State.valueOf("New")); // State.NEW console.log(State.valueByName("NEW")); // State.NEW console.log(State.ACTIVE.enumName); // ACTIVE 

并非出于好奇,还额外导入了IStaticEnum,这在任何地方都没有使用(请参见上面的示例)。 在命运特别糟糕的TypeScript 2.6.2版本中,您需要明确指定它。 有关此主题的错误。

合计


如果您长时间受苦,某些事情将会解决。 链接到github并获得此处所做工作的结果 。 对我自己来说,我发现TypeScript是一种功能强大的语言。 这些机会太多了,您可以淹没其中。 而谁不想下沉,就会学游泳。 如果您回到转移的主题,则可以看到其他人如何进行转移:


写有关您的工作的信息,我认为社区会对此感兴趣。 谢谢大家的耐心和关注。

Source: https://habr.com/ru/post/zh-CN420041/


All Articles