Как обрабатывать обратное отображение строковых перечислений с различными строковыми литералами в машинописи? - PullRequest
0 голосов
/ 04 января 2019

Были похожие вопросы, например:

Мой пример отличается от других связанных вопросов, потому что я использую строковый литерал, который отличается от имени члена enum.

У меня есть следующее перечисление:

export enum AttackType {
  MELEE = <any>'close', // I had to use <any> to make it work at all
  RANGED = <any>'far'
}

Я хочу получить правильное перечисление на основе назначенного строкового литерала. Я думал, что AttackType['close'] будет таким же, как AttackType.MELEE, но это не так. Первый печатает MELEE, а второй печатает close, делая следующее утверждение ложным

AttackType['close']===AttackType.MELEE

Итак, если у меня есть следующий класс:

export class Hero{
 attackType: AttackType;
 constructor(){
  this.attackType = AttackType['close']; // no errors, and prints MELEE,
  this.attackType = AttackType.MELEE; // no errors, and prints close,
  this.attackType = AttackType[AttackType['close']]; // compile error.
  this.attackType = AttackType[<string>AttackType['close']]; // no errors, and prints close.

 }
}

Мне интересно, как мне решить эту проблему. Как я могу убедиться, что attackType назначен правильно, когда я знаю только строковый литерал (например, 'close')?

Я мог бы использовать «странный» способ (AttackType[<string>AttackType['close']]), чтобы назначить правильное значение перечисления, чтобы оно совпадало с AttackType.MELEE, но я не уверен, что это хороший способ сделать это.

1 Ответ

0 голосов
/ 04 января 2019

Меня не устраивает идея попытаться обмануть компилятор в создании обратных отображений для строковых перечислений с использованием этого утверждения типа <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).

Хорошо, надеюсь, это поможет; удачи!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...