#javascript #typescript #enums
#javascript #typescript #перечисления
Вопрос:
Я помещаю перечисление в класс, чтобы я мог сопоставить его значения в 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 amp;amp; 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());
}
Я хотел бы получить прямой доступ к значению enum, я хочу сделать это
MapColor.RED // 0
MapColor.RED.toString() // 'RED'
Возможно ли создать динамические свойства для класса? Если возможно, как это будет сделано?
Комментарии:
1. Вполне возможно, используя дженерики, но зачем вам это нужно?
2.Я хочу сделать это
MapColor.RED // 0
MapColor.RED.toString() // 'RED'
3. Да, это можно сделать, но использовать для этого класс бессмысленно. Фабричную функцию, которая возвращает объект-оболочку, намного проще написать, а типы менее сложны.
4. Подождите секунду, забудьте о классах на минуту. Вы хотите
MapColor.RED
быть примитивным числовым значением0
и хотитеMapColor.RED.toString()
вывести строку"RED"
? Например, вы хотитеMapColor.RED === Color.RED
бытьtrue
, ноString(MapColor.RED)
быть"RED"
, верно? Невозможно.5. Я собираюсь полностью игнорировать невозможное редактирование вопроса, потому что в противном случае ответ будет просто «нет», и я уже начал писать ответ на возможную версию этого 😛
Ответ №1:
В СТОРОНУ:
Я игнорирую запрос, который MapColor.RED
должен быть 0
, но MapColor.RED.toString()
должен оцениваться "RED"
. Если вы хотите MapColor.RED === Color.RED
быть true
, так что MapColor.RED
это number
(примитивный тип данных), значение которого равно 0
, вам совершенно невозможно получить "RED"
в качестве его строкового значения, поскольку (0).toString()
это просто "0"
. Если вы хотите MapColor.RED
быть Number
(объектом-оболочкой), вы могли бы сделать это как Object.assign(Number(0), {toString(){return "RED"})
, но то, что из этого получается, ужасно. Он действует как 0
( MapColor.RED 5 === 5
) , за исключением случаев, когда это не так, поскольку MapColor.RED === 0
is false
и someObject[MapColor.RED]
будет обращаться к RED
свойству someObject
, а не к 0
свойству. Таким образом, вы либо не можете этого сделать, либо можете, но не должны. Поэтому я притворяюсь, что его не существует. 🙈
ОТВЕТ:
Вы можете реализовать такой класс, скопировав свойства enumeration
непосредственно this
внутри конструктора (или init()
метода, я полагаю). И вы можете описать тип такого конструктора класса, используя обобщения через пересечение. Чего вы не можете сделать, так это заставить компилятор проверить, что реализация соответствует описанию. Поэтому вам нужно будет использовать утверждения типа в нескольких местах, чтобы сообщить компилятору, что ваши экземпляры класса и конструктор класса соответствуют рассматриваемому поведению:
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 amp;amp; 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> amp; T;
const EnumMap = _EnumMap as
new <T extends Record<keyof T, string | number>>(enumeration: T) => EnumMap<T>;
Переименованный EnumMap
является общим по типу T
enumeration
переданного ему объекта. Внутри init()
я пишу this[key] = val
, но компилятор не знает, что this
у него должны быть ключи, соответствующие key
, поэтому я утверждаю this as any
, чтобы ослабить предупреждения компилятора. Я также изменил типы вывода всех ваших существующих методов, чтобы более точно соответствовать тому, что вы получаете от них… но вы не просили об этом, поэтому сохраните any
и Object
(или, возможно object
), если необходимо.
Написав type EnumMap<T> = _EnumMap<T> amp; T
, я говорю, что an EnumMap<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
Для меня это выглядит хорошо.