Используйте Mixins с типами объединения в Typescript

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

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 не имеет типSpriteEntity. Либо вы имеете в виду, что IEntityDefinition['type'] должно быть Constructor<BlackbirdEntity>, либо вы имеете в виду поместить пример типа SpriteEntity в качестве type свойства clickableEntity.

jcalz 07.04.2019 21:23

Да, я в основном имею в виду, что экземпляр SpriteEntity является type свойством clickableEntity. Как мне выразить это с помощью определений типа TS? Или как присоединить типSpriteEntity к стоимостьSpriteEntity?

Tumetsu 07.04.2019 22:01

Чтобы уточнить, я хочу установить стоимостьSpriteEntity в свойство typeclickableEntity. Вместо тип свойство должно быть скорее класс сущности и т. д., поскольку я хочу, чтобы оно ссылалось на класс, который позже создается. Объект clickableEntity — это объект конфигурации для фабрики.

Tumetsu 07.04.2019 22:09

Итак, вы хотите использовать Constructor<BlackbirdEntity> вместо BlackbirdEntity в своем определении IEntityDefinition.

jcalz 07.04.2019 22:11
Зод: сила проверки и преобразования данных
Зод: сила проверки и преобразования данных
Сегодня я хочу познакомить вас с библиотекой Zod и раскрыть некоторые ее особенности, например, возможности валидации и трансформации данных, а также...
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Мне нравится библиотека Mantine Component , но заставить ее работать без проблем с Remix бывает непросто.
Угловой продивер
Угловой продивер
Оригинал этой статьи на турецком языке. ChatGPT используется только для перевода на английский язык.
TypeScript против JavaScript
TypeScript против JavaScript
TypeScript vs JavaScript - в чем различия и какой из них выбрать?
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Не все нужно хранить на стороне сервера. Иногда все, что вам нужно, это постоянное хранилище на стороне клиента для хранения уникальных для клиента...
Что такое ленивая загрузка в Angular и как ее применять
Что такое ленивая загрузка в Angular и как ее применять
Ленивая загрузка - это техника, используемая в Angular для повышения производительности приложения путем загрузки модулей только тогда, когда они...
0
4
528
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Ваша проблема в том, что 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 компилируемой без ошибок.

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

Ааа, это определенно имеет смысл! По какой-то причине я не понял, что мне нужно указать тип в качестве конструктора и случайно использовать экземпляр. Спасибо!

Tumetsu 08.04.2019 08:21

Другие вопросы по теме