在 Typescript 中将 Mixins 与联合类型结合使用

Use Mixins with union types in Typescript

我有一个简单的系统,我通过从单独的基础 classes 继承它们来生成 classes,然后将另一个 class 混合到它们中的每一个。这是我的混合 class:

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');
    }
  };
}

这个 mixin 在另一个模块中使用来创建几个 classes:

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;

如您所见,我导出了实际创建的 classes 及其类型以及一个额外的联合类型 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's parent class. So is there any way to tell the compiler this kind of type should be ok, even if their parents' 定义不同的属性?

你的问题是 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 变量初始化编译没有错误。

希望对您有所帮助;祝你好运!