首页 > 解决方案 > 是否可以为类创建动态属性?

问题描述

我将枚举包装在一个类中,以便可以在 TypeScript 中映射它的值

enum Color {
    RED = 0,
    GREEN = 1,
    BLUE = 2
}

const MapColor = new EnumMap(Color); // Wrapper

console.log(MapColor.map()) // [{ "key": "RED", "value": 0}, { "key": "GREEN","value": 1}, { "key": "BLUE",  "value": 2}] 
console.log(MapColor.entries()) // [["RED", 0], ["GREEN", 1], ["BLUE", 2]] 
console.log(MapColor.keys()) // ["RED", "GREEN", "BLUE"] 
console.log(MapColor.values()) //  [0, 1, 2] 

包装类

class EnumMap {
  private _map = new Map<string, string | number>();
  public enum: any;

  constructor(enumeration: any) {
    this.init(enumeration);
    return this;
  }

  private init(enumeration: any) {
    this._map = new Map<string, string | number>();

    for (let key in enumeration) {
      if (!isNaN(Number(key))) continue;
      const val = enumeration[key] as string | number;
      if (val !== undefined && val !== null) this._map.set(key, val);
    }

    this.enum = enumeration;
  }

  map = (): Object => Array.from(this._map.entries()).map((m) => ({ key: m[0], value: m[1] }));
  entries = (): any[] => Array.from(this._map.entries());
  keys = (): any[] => Array.from(this._map.keys());
  values = (): any[] => Array.from(this._map.values());
}

我想直接访问一个枚举值,我想这样做

MapColor.RED  // 0
MapColor.RED.toString()  // 'RED'

是否可以为类创建动态属性?如果可能的话,它会怎么做?

标签: javascripttypescriptenums

解决方案


在旁边:

我忽略了但评估为MapColor.RED的请求。如果你想成为,那么就是值是 的 (原始数据类型),你完全不可能得到它的字符串值,因为 只是。如果你想成为一个(包装对象),你可以把它做为,但结果很糟糕。它有点像( ),除非它不这样做,因为它是,并且将访问 的属性,而不是0MapColor.RED.toString()"RED"MapColor.RED === Color.REDtrueMapColor.REDnumber0"RED"(0).toString()"0"MapColor.REDNumberObject.assign(Number(0), {toString(){return "RED"})0MapColor.RED + 5 === 5MapColor.RED === 0falsesomeObject[MapColor.RED]REDsomeObject0财产。因此,您要么不能这样做,要么可以但不应该这样做。所以我假装它不存在。


回答:

您可以通过将 的属性直接复制到构造函数(或方法,我猜)内部来实现这样的类。您可以通过交集使用泛型来描述此类构造函数的类型。你不能做的是让编译器验证实现是否符合描述。因此,您需要在多个地方使用类型断言来告诉编译器您的类实例和类构造函数符合相关行为:enumerationthisinit()

class _EnumMap<T extends Record<keyof T, string | number>> {
    private _map = new Map<keyof T, T[keyof T]>();
    public enum!: T;

    constructor(enumeration: T) {
        this.init(enumeration);
        return this;
    }

    private init(enumeration: T) {
        this._map = new Map<keyof T, T[keyof T]>();

        for (let key in enumeration) {
            if (!isNaN(Number(key))) continue;
            const val = enumeration[key];
            if (val !== undefined && val !== null) {
                this._map.set(key, val);
                (this as any)[key] = val; // set value here
            }
        }

        this.enum = enumeration;
    }

    map = () => Array.from(this._map.entries())
        .map((m) => ({ key: m[0], value: m[1] })) as
        Array<{ [K in keyof T]: { key: K, value: T[K] } }[keyof T]>;
    entries = () => Array.from(this._map.entries()) as
        Array<{ [K in keyof T]: [K, T[K]] }[keyof T]>;
    keys = () => Array.from(this._map.keys()) as Array<keyof T>;
    values = () => Array.from(this._map.values()) as Array<T[keyof T]>;
}

我已将名称重命名为EnumMap_EnumMap并在最后恢复EnumMap名称:

type EnumMap<T extends Record<keyof T, string | number>> = _EnumMap<T> & T;
const EnumMap = _EnumMap as 
  new <T extends Record<keyof T, string | number>>(enumeration: T) => EnumMap<T>;

重命名的对象在传递给它的对象EnumMap类型中是通用的。在里面我写了,但是编译器不知道应该有对应的键,所以我断言放松编译器警告。我也更改了所有现有方法的输出类型,以更正确地对应于您从中得到的内容......但您没有要求这样做,所以如果必须,请保留和(或可能)。Tenumerationinit()this[key] = valthiskeythis as anyanyObjectobject

通过编写type EnumMap<T> = _EnumMap<T> & T,我是说 anEnumMap<T>既充当您的原始类,充当T. 因此,如果T看起来有点像{RED: 0, GREEN: 1, BLUE: 2},那么EnumMap<T>也将具有这些属性。通过写作const EnumMap = _EnumMap as new<T>(enumeration: T) => EnumMap<T>,我是说原始类构造函数已被复制到一个名为 的值EnumMap,并且可以被视为 new 实例的类构造函数EnumMap<T>


让我们测试一下:

enum Color {
    RED = 0,
    GREEN = 1,
    BLUE = 2
}

const MapColor = new EnumMap(Color); // Wrapper

console.log(MapColor.map()) // [{ "key": "RED", "value": 0}, 
  // { "key": "GREEN","value": 1}, { "key": "BLUE",  "value": 2}] 
console.log(MapColor.entries()) // [["RED", 0], ["GREEN", 1], ["BLUE", 2]] 
console.log(MapColor.keys()) // ["RED", "GREEN", "BLUE"] 
console.log(MapColor.values()) //  [0, 1, 2] 
console.log(MapColor.RED) // 0
console.log(MapColor.GREEN) // 1
console.log(MapColor.BLUE) // 2

对我来说看上去很好。


Playground 代码链接


推荐阅读