Альтернатива использованию перечислений для указания типов в TypeScript?

#javascript #typescript #generics #subclass

#javascript #typescript #общие #подкласс

Вопрос:

В моем приложении я разрабатываю Entity-Component-System, и мне нужен способ получения объектов по их типу с карты. В настоящее время я делаю это:

 class Entity {
  components: Map<CT, Component>;

  constructor() {
    this.components = new Map();
  }

  get(componentType: CT): any {
    return this.components.get(componentType);
  }
}

const enum CT {
  Null,
  Health,
  Position,
  // etc..
}

class Component {
  type: CT;

  constructor(type: CT) {
    this.type = type;
  }
}

class HealthComponent extends Component {
  amount: number;
  
  constructor() {
    super(CT.Health);

    this.amount = 0;
  }
}
 

И затем вот несколько примеров клиентского кода:

 let healthComponent = new HealthComponent();
healthComponent.amount = 100;

let entity = new Entity();
entity.components.set(healthComponent.type, healthComponent);

// Later, elsewhere..
let health = entity.get(CT.Health);
console.log(health.amount);
 

Все это работает, но немного громоздко. Каждый новый компонент, который я создаю, требует, чтобы я вносил новую запись в CT перечисление, и чтобы я убедился, что она соответствует в конструкторе нового типа. В идеале я хотел бы иметь возможность просто делать что-то вроде:

 let healthComponent = new HealthComponent();
healthComponent.amount = 100;

let entity = new Entity();
entity.components.set(HealthComponent, healthComponent);

// Later, elsewhere..
let health = entity.get(HealthComponent);
console.log(health.amount);
 

Но я не уверен, возможно ли это. Можно ли это сделать?

Ответ №1:

Я бы просто использовал string в качестве ключа и сделал что-то вроде:

 class Entity {
  components: Map<string, Component>;

  constructor() {
    this.components = new Map();
  }

  get(componentType: string): any {
    return this.components.get(componentType);
  }
}

 

А затем используйте name свойство класса следующим образом:

 let health = entity.get(Health.name);
console.log(health.amount);
 

Однако вам нужно будет указать target в своем tconfig файле to es6 или esnext file, чтобы он заработал.
Это не будет работать для интерфейсов, поэтому, если вы планируете их использовать, это также не очень хороший подход.

Комментарии:

1. Хм, да, я слышал, что у этого подхода могут быть проблемы с совместимостью, а также кажется, что производительность при этом может быть хуже, поскольку ключи представляют собой несколько длинные строки вместо простых целых чисел. Тем не менее, я мог бы это сделать … не уверен..

2. Подождите, какой длины эти строки, что это влияет на производительность? Мне это кажется очень маловероятным.

3. @RyanPeschel Строки содержат именно ваше имя класса. Разница с int не так уж велика. С другой стороны, если у вас есть два класса с одинаковыми именами, этот подход также не будет работать. С третьей (?) стороны, Что вы говорите о совместимости — где вы собираетесь запускать этот код? Если на сервере, просто упакуйте его в docker, и у вас не возникнет проблем. Если в браузере, то это не очень хорошая идея.