Меня не устраивает идея попытаться обмануть компилятор в создании обратных отображений для строковых перечислений с использованием этого утверждения типа <any>
. Обратное сопоставление строк преднамеренно опущено, как сказано @ RyanCavanaugh в актуальной проблеме GitHub :
Если бы мы предоставили обратную карту автоматически (что, кстати, не обязательно является неоднозначным!), То у вас не будет возможности отличить ключи от значений, если во время выполнения не будет создан совершенно отдельный объект. Тривиально написать функцию, которая создает обратную карту, если вы этого хотите; наша цель состоит в том, чтобы испускать как можно меньше кода, поэтому имеет смысл просто испускать необходимые данные (чтобы уменьшить размер кода) и позволить вам создавать обратную карту по мере необходимости.
Я думаю, что если вы продолжите использовать этот трюк, вы обнаружите, что вам нужно делать утверждения, как вы делаете, и вы не должны удивляться, если какая-то будущая версия TypeScript нарушит его полностью.
Но если руководитель разработки для TypeScript говорит, что «написать функцию, которая создает обратную карту, тривиально», то мы можем попробовать это и посмотреть, как это происходит. Так как бы вы пошли об этом? Во время выполнения вам действительно нужно просто перебрать записи объекта enum и создать новый объект с переключенными ключами и значениями. И если вам нужна прямая и обратная карта в одном и том же объекте, вы можете объединить свойства из обычного перечисления в обращенное:
type Entries<T extends object> = { [K in keyof T]: [K, T[K]] }[keyof T]
function reverseEnum<E extends Record<keyof E, string | number>>(
e: E
): { [K in E[keyof E]]: Extract<Entries<E>, [any, K]>[0] };
function reverseEnum(
e: Record<string | number, string | number>
): Record<string | number, string | number> {
const ret: Record<string | number, string | number> = {};
Object.keys(e).forEach(k => ret[e[k]] = k);
return ret;
}
function twoWayEnum<E extends Record<keyof E, string | number>>(e: E) {
return Object.assign(reverseEnum(e), e);
}
Подпись для reverseEnum()
включает в себя немного жонглирования типов. Функция типа Entries<T>
превращает тип объекта T
в объединение пар ключ-значение. Например, Entries<{a: string, b: number}>
оценивается как ["a",string] | ["b",number]
. Тогда тип возврата reverseEnum()
- это сопоставленный тип , ключи которого получены из перечисления значения , а значения получены из ключа для соответствующей записи путем извлечения Это. Посмотрим, работает ли он:
enum AttackType {
MELEE = 'close',
RANGED = 'far'
}
const TwoWayAttackType = twoWayEnum(AttackType);
// const TwoWayAttackType = {
// close: "MELEE";
// far: "RANGED";
// } & typeof AttackType
// might as well make a type called TwoWayAttackType also,
// corresponding to the property values of the TwoWayAttackType object
type TwoWayAttackType = AttackType | keyof typeof AttackType
console.log(TwoWayAttackType.close); // "MELEE"
console.log(TwoWayAttackType[TwoWayAttackType.far]) // "far"
Вы можете видеть, что значение TwoWayAttackType
имеет тот же тип, что и константа перечисления AttackType
, с дополнительными свойствами {close: "MELEE", far: "RANGED"}
. Одна из сложностей заключается в том, что TypeScript не генерирует автоматически тип с именем TwoWayAttackType
, соответствующий типам свойств константы TwoWayAttackType
, поэтому, если вам нужно, мы должны сделать это вручную, как я делал выше.
Теперь вы сможете создать класс по своему усмотрению без ошибок типов:
class Hero {
attackType: TwoWayAttackType;
constructor() {
this.attackType = TwoWayAttackType['close'];
this.attackType = TwoWayAttackType.MELEE;
this.attackType = TwoWayAttackType[TwoWayAttackType['close']];
}
}
Обратите внимание, что если этот метод работает для вас, вы всегда можете переименовать значения / типы выше, чтобы то, что я называю TwoWayAttackType
, было просто AttackType
(и тогда, возможно, то, что я называю AttackType
, было бы что-то вроде OneWayAttackType
или BaseAttackType
).
Хорошо, надеюсь, это поможет; удачи!