У меня есть простая система, в которой я генерирую классы, наследуя их от отдельных базовых классов, а затем добавляя к каждому из них другой класс. Вот мой класс миксина:
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
. Иногда я буду использовать переменные, которые могут быть любого из сгенерированных типов, так как в этих случаях эти экземпляры управляются их общим смешанным интерфейсом.
Далее у меня есть следующее простое определение, в котором используется тип union:
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
. Итак, есть ли способ сказать компилятору, что этот тип должен быть в порядке, даже если определение их родителей отличается?
Да, я в основном имею в виду, что экземпляр SpriteEntity
является type
свойством clickableEntity
. Как мне выразить это с помощью определений типа TS? Или как присоединить типSpriteEntity
к стоимостьSpriteEntity
?
Чтобы уточнить, я хочу установить стоимостьSpriteEntity
в свойство type
clickableEntity
. Вместо тип свойство должно быть скорее класс сущности и т. д., поскольку я хочу, чтобы оно ссылалось на класс, который позже создается. Объект clickableEntity
— это объект конфигурации для фабрики.
Итак, вы хотите использовать Constructor<BlackbirdEntity>
вместо BlackbirdEntity
в своем определении IEntityDefinition
.
Ваша проблема в том, что IEntityDefinition
хотел, чтобы его свойство type
было пример из BlackbirdEntity
, а не конструктор из единицы. Вы можете запутаться, потому что для class
конструктор стоимость обычно имеет то же имя, что и экземпляр тип, хотя это не одно и то же.
В любом случае, у вас уже есть псевдоним типа Constructor<T>
, так что давайте воспользуемся им:
export interface IEntityDefinition {
name: string;
components: Component[];
type: Constructor<BlackbirdEntity>; // you want a constructor, not an instance here
}
Это должно сделать вашу инициализацию переменной clickableEntity
компилируемой без ошибок.
Надеюсь, это поможет; удачи!
Ааа, это определенно имеет смысл! По какой-то причине я не понял, что мне нужно указать тип в качестве конструктора и случайно использовать экземпляр. Спасибо!
стоимость
SpriteEntity
не имеет типSpriteEntity
. Либо вы имеете в виду, чтоIEntityDefinition['type']
должно бытьConstructor<BlackbirdEntity>
, либо вы имеете в виду поместить пример типаSpriteEntity
в качествеtype
свойстваclickableEntity
.