Используйте Mixins с типами объединения в Typescript - PullRequest
0 голосов
/ 07 апреля 2019

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

type Constructor<T = {}> = new (...args: any[]) => T;

/**
 * Based on the Mixin idea explained here:
 * https://mariusschulz.com/blog/typescript-2-2-mixin-classes
 *
 * @param base
 * @constructor
 */
export function EntityServices<TBase extends Constructor>(base: TBase) {
  return class extends base {
    private _components = {};

    public addComponent(component: Component) {
      throw new Error('Not implemented');
    }

    public removeComponent(component: Component) {
      throw new Error('Not implemented');
    }
  };
}

Этот миксин используется в другом модуле для создания нескольких классов:

class ContainerEntityBase extends Phaser.GameObjects.Container {}
class ImageEntityBase extends Phaser.GameObjects.Image {}
class SpriteEntityBase extends Phaser.GameObjects.Sprite {}
class TextEntityBase extends Phaser.GameObjects.Text {}

export const ContainerEntity = EntityServices(ContainerEntityBase);
export const ImageEntity = EntityServices(ImageEntityBase);
export const SpriteEntity = EntityServices(SpriteEntityBase);
export const TextEntity = EntityServices(TextEntityBase);

// Type definitions have to be exported separately so that they can be used as types elsewhere, not as values
// Same name with values (classes) does not matter since TS stores values and types into separate
// namespaces.
export type ContainerEntity = InstanceType<typeof ContainerEntity>;
export type ImageEntity = InstanceType<typeof ImageEntity>;
export type SpriteEntity = InstanceType<typeof SpriteEntity>;
export type TextEntity = InstanceType<typeof TextEntity>;
export type BlackbirdEntity = ContainerEntity | ImageEntity | SpriteEntity | TextEntity;

Как видите, я экспортировал как реально созданные классы, так и их типы с одним дополнительным типом объединения BlackBirdEntity. Иногда я буду использовать переменные, которые могут быть любого из сгенерированных типов, так как в этих случаях эти экземпляры обрабатываются общим смешанным интерфейсом.

Далее у меня есть следующее простое определение, которое использует тип объединения:

import { Component } from '../core/Component';
import { BlackbirdEntity } from '../core/entities';

export interface IEntityDefinition {
  name: string;
  components: Component[];
  type: BlackbirdEntity;
}

И я использую его таким образом, чтобы создать объект, который реализует указанный интерфейс:

import { SpriteEntity } from '../core/entities';
import { IEntityDefinition } from './EntityDefinition';

const clickableEntity: IEntityDefinition = {
  components: [],
  name: 'Clickable',
  type: SpriteEntity
};

Однако, это дает мне следующую ошибку в IDE с выделенным SpriteEntity:

TS2322: Type '{ new (...args: any[]): EntityServices<typeof SpriteEntityBase>.(Anonymous class); prototype: EntityServices<any>.(Anonymous class); } & typeof SpriteEntityBase' is not assignable to type 'BlackbirdEntity'.   Type '{ new (...args: any[]): EntityServices<typeof SpriteEntityBase>.(Anonymous class); prototype: EntityServices<any>.(Anonymous class); } & typeof SpriteEntityBase' is not assignable to type 'EntityServices<typeof TextEntityBase>.(Anonymous class) & TextEntityBase'.     Type '{ new (...args: any[]): EntityServices<typeof SpriteEntityBase>.(Anonymous class); prototype: EntityServices<any>.(Anonymous class); } & typeof SpriteEntityBase' is missing the following properties from type 'EntityServices<typeof TextEntityBase>.(Anonymous class)': _components, addComponent, removeComponent

Почему? И как это исправить? Кажется, ошибка указывает на то, что SpriteEntity не содержит свойств, которые в действительности принадлежат родительскому классу TextEntity. Так есть ли способ сказать компилятору, что этот тип типа должен быть в порядке, даже если определение их родителей отличается?

1 Ответ

1 голос
/ 07 апреля 2019

Ваша проблема в том, что IEntityDefinition хотел, чтобы его свойство type было экземпляром из BlackbirdEntity, а не конструктором из одного.Вы можете быть сбиты с толку, потому что для class конструктор value обычно разделяет имя с экземпляром type , хотя они не одно и то же .

В любом случае, у вас уже есть псевдоним типа Constructor<T>, поэтому давайте использовать его:

export interface IEntityDefinition {
  name: string;
  components: Component[];
  type: Constructor<BlackbirdEntity>; // you want a constructor, not an instance here
}

Это должно заставить вашу clickableEntity инициализацию переменной скомпилироваться без ошибок.

Надеюсь, это поможет;удачи!

...