打字稿可变泛型类,接受扩展公共基础的可变数量的类对象?
Posted
技术标签:
【中文标题】打字稿可变泛型类,接受扩展公共基础的可变数量的类对象?【英文标题】:Typescript variadic generic class, accepting a variable number of class objects which extend a common base? 【发布时间】:2021-09-26 11:14:18 【问题描述】:我正在尝试在 typescript 中设计一个实体组件系统。我的目标是创建一个可变参数泛型抽象 System
类,它将组件类作为泛型参数。
这个想法是派生的System
类可以用它们使用的组件来声明。然后,他们可以使用此信息(组件类对象)从所有实体列表中提取他们操作的实体子集。该子集是具有所需组件的实体集。
对于上下文,我首先包含了组件和实体代码:
interface IComponent
owner: Entity | null;
type ComponentClass<C extends IComponent> = new (args: unknown[]) => C;
abstract class Entity
public readonly name: string;
/** The pixijs scene node (pixijs display object). */
private _sceneNode: unknown;
protected _components: IComponent[] = [];
constructor(name: string)
this.name = name;
public get components(): IComponent[]
return this._components;
public addComponent(component: IComponent): void
this._components.push(component);
component.owner = this;
public getComponent<C extends IComponent>(componentClass: ComponentClass<C>): C
for (const component of this._components)
if (component instanceof componentClass)
return component as C;
throw new Error(`component '$componentClass.name' missing from entity $this.constructor.name`);
public removeComponent<C extends IComponent>(componentClass: ComponentClass<C>): void
const removeList: number[] = [];
this._components.forEach((component, index) =>
if (component instanceof componentClass)
removeList.push(index);
);
removeList.forEach(index =>
this._components.splice(index, 1);
);
public hasComponent<C extends IComponent>(componentClass: ComponentClass<C>): boolean
return this._components.some(component =>
return component instanceof componentClass;
);
interface ISystem
onSpawnEntity(entity: Entity): void;
onDestroyEntity(entity: Entity): void;
pullEntities(entities: Entity[]): void;
update(dt_s: number) : void;
我试图使它成为一个可变参数泛型,可以采用任意数量的组件
类,每个类都扩展IComponent
。目前,它只需要 2 个,这证明了我想要实现的目标:
abstract class System<C0 extends IComponent, C1 extends IComponent> implements ISystem
protected _targets: [ComponentClass<C0>, ComponentClass<C1>];
protected _subjects: Entity[] = [];
constructor(targets: [ComponentClass<C0>, ComponentClass<C1>])
this._targets = targets;
public pullEntities(entities: Entity[]): void
entities.forEach(entity =>
if(this.isEntitySubject(entity))
this._subjects.push(entity);
);
public onSpawnEntity(entity: Entity): void
if(this.isEntitySubject(entity))
this._subjects.push(entity);
public onDestroyEntity(entity: Entity): void
if(this.isEntitySubject(entity))
public update(dt_s: number) : void
this._subjects.forEach(entity =>
const c0 = entity.getComponent(this._targets[0]);
const c1 = entity.getComponent(this._targets[1]);
this.updateEntity(c0, c1);
)
private isEntitySubject(entity: Entity): boolean
return entity.hasComponent(this._targets[0]) &&
entity.hasComponent(this._targets[1]);
// the idea is that this is the only function systems will have to implement themselves,
// ideally want the args to be a variadic array of the component instances which the
// system uses.
protected updateEntity(c0: C0, c1: C1)
abstract class World
protected _systems: ISystem[] = [];
protected _entities: Entity[] = [];
public feedEntities(): void
this._systems.forEach(system =>
system.pullEntities(this._entities);
);
public updateSystems(): void
this._systems.forEach(system =>
system.update(20);
);
我还包含了示例用法以提供更多上下文:
////////////////////////////////////////////////////////////////////////////////
// EXAMPLE USAGE
////////////////////////////////////////////////////////////////////////////////
class PhysicsComponent implements IComponent
public owner: Entity | null;
public x: number;
public y: number;
public mass: number;
class CollisionComponent implements IComponent
public owner: Entity | null;
public bounds: x: number, y: number, width: number, height: number;
// creating a new system, defining the component classes it takes.
class PhysicsSystem extends System<PhysicsComponent, CollisionComponent>
protected updateEntity(physics: PhysicsComponent, collision: CollisionComponent)
physics.x += 1;
physics.y += 1;
console.log(`entity: $physics.owner.name has position: x: $physics.x, y: $physics.y`);
class Person extends Entity
constructor(name: string)
super(name);
const physics = new PhysicsComponent();
physics.x = 20;
physics.y = 40;
this.addComponent(physics);
const collision = new CollisionComponent();
collision.bounds =
x: 0, y: 0, width: 100, height: 100
;
this.addComponent(collision);
class GameWorld extends World
constructor()
super();
this._systems.push(new PhysicsSystem([PhysicsComponent, CollisionComponent]));
const e0 = new Person("jim");
const e1 = new Person("steve");
const e2 = new Person("sally");
this._entities.push(e0, e1, e2);
this.feedEntities();
const world = new GameWorld();
setInterval(() =>
world.updateSystems();
, 300);
我正在努力实现的目标是否可能?
【问题讨论】:
【参考方案1】:TypeScript 没有“可变参数泛型”,但它有 Tuple types
所以你的System
类可以是
type ComponentClasses<T extends IComponent[]> = [K in keyof T]: T[K] extends IComponent ? ComponentClass<T[K]> : never ;
abstract class System<TComponents extends IComponent[]> implements ISystem
protected _targets: ComponentClasses<TComponents>;
protected _subjects: Entity[] = [];
constructor(targets: ComponentClasses<TComponents>)
this._targets = targets;
protected abstract updateEntity(...components: TComponents): void;
解释:
TComponents extends IComponent[]
表示TComponent
必须是IComponent
的数组(或元组),例如PhysicsComponent[]
或[PhysicsComponent, CollisionComponent]
(我没有测试,但我的代码的其他部分应该只适用于元组类型)
为了将TComponents
转换为ComponentClass
es,我使用了一个辅助类型ComponentClasses
,它是一个Mapped type,特别是,映射一个元组类型只映射它的数字键,意味着ComponentClasses<[PhysicsComponent, CollisionComponent]>
将返回@987654335 @
要使updateEntity
方法接受可变数量的参数,使用Rest Parameters 语法。在 TypeScript 中,它允许使用元组类型标记多个参数。
PhysicsSystem
的示例:
class PhysicsSystem extends System<[PhysicsComponent, CollisionComponent]>
protected override updateEntity(physics: PhysicsComponent, collision: CollisionComponent)
physics.x += 1;
physics.y += 1;
console.log(`entity: $physics.owner!.name has position: x: $physics.x, y: $physics.y`);
如果更改physics
或collision
参数的类型,将无法编译。
在GameWorld
:
this._systems.push(new PhysicsSystem([PhysicsComponent, CollisionComponent]));
如果更改参数数组,它也不会编译。
【讨论】:
只想说声谢谢正是我需要的答案:)以上是关于打字稿可变泛型类,接受扩展公共基础的可变数量的类对象?的主要内容,如果未能解决你的问题,请参考以下文章
如何在 typescript 中为泛型类编写扩展作为 getter
Kotlin泛型总结 ★ ( 泛型类 | 泛型参数 | 泛型函数 | 多泛型参数 | 泛型类型约束 | 可变参数结合泛型 | out 协变 | in 逆变 | reified 检查泛型参数类型 )