如何让一个Typescript函数返回一个子类?



我正在用TypeScript开发一个实体-组件-系统,其中实体包含其组件的映射。CT代表ComponentType,代码如下:

class Entity {
components: Map<CT, Component>;
constructor() {
this.components = new Map();
}
get(componentType: CT): Component {
return this.components.get(componentType);
}
}
const enum CT {
Position,
Health // etc..
}
class Component {
type: CT;
constructor(type: CT) {
this.type = type;
}
}
class HealthComponent extends Component {
amount: number;

constructor() {
super(CT.Health);
this.amount = 0;
}
}

问题是当我做这样的事情时:

let healthComponent = new HealthComponent();
healthComponent.amount = 100;
let entity = new Entity();
entity.components.set(healthComponent.type, healthComponent);
let position = entity.get(CT.Health);
console.log(health.amount);
我得到这个错误:Property 'amount' does not exist on type 'Component'

如果我将Entity.get函数更改为返回any,则错误消失:

get(componentType: CT): any {
return this.components.get(componentType);
}

但是我不再得到我想要的代码补全。

是否有可能让它返回正确的类型,这样我就可以进行代码完成和错误检测?

class Component {
// error: 'amount' does not exist on type 'Component'
amount: number; // <-- NOTE: moved amount here from HealthComponent
type: CT;
constructor(type: CT) {
this.type = type;
}
}
class HealthComponent extends Component {
// removed amount from here

constructor() {
super(CT.Health);
this.amount = 0;
}
}

我设法用TypeScript的条件类型拼凑了一些东西。这不是理想的,但它可能对你有用:

让我们先声明枚举和组件类:

const enum CT {
Position,
Health
}
class HealthComponent {
amount: number;

constructor() {
this.amount = 0;
}
}
class PositionComponent {
position: number;

constructor() {
this.position = 1;
}
}

然后是将枚举值映射到组件类的条件类型:

type TypeMapper<T extends CT> = T extends CT.Position 
? PositionComponent 
: HealthComponent;

这是一个泛型类型,基于它的类型参数T计算为PositionComponentHealthComponent类。

现在是实体类:

class Entity {
private components: Map<CT, TypeMapper<CT>>;
constructor() {
this.components = new Map();
}
set<T extends CT>(ct: T, component: TypeMapper<T>) {
this.components.set(ct, component);
}
get<T extends CT>(ct: T): TypeMapper<T> {
return this.components.get(ct) as any; //a little lie here to shut up the TS compiler
}
}

现在,当您实例化所有内容时,类型应该为您正确对齐:

const entity = new Entity();
entity.set(CT.Health, new HealthComponent());
const health = entity.get(CT.Health);
health.amount //and the amount prop should show up in intellisense correctly.

正如我所说的,这不是理想的,因为它依赖于使用any,也取决于你拥有的组件类的数量,你的TypeMapper类型可以变得巨大,但至少Component基类和super()对它的构造函数的调用不再需要,所以你在那里赢得了一些代码行。

这是TS playground的链接。

最新更新