【问题标题】:How to have a Typescript function return a subclass?如何让 Typescript 函数返回子类?
【发布时间】:2021-03-02 20:35:21
【问题描述】:

我正在 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);
}

但是我不再得到我想要的代码完成。

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

【问题讨论】:

  • PositionComponent 在哪里使用?
  • @Vishnudev 好点 - 刚刚将其添加到示例中以进行澄清
  • 你能if (position is PositionComponent) console.log((position as PositionComponent).x)吗?
  • @DM 我想尽可能避免这种情况,因为我已经知道类型。
  • 你知道类型,但你的编译器不知道。 Map 只包含没有 x 属性的 Components。您也许可以尝试Map&lt;CT, Component | PositionComponent&gt;,但我怀疑这是正确的 TS 方法。

标签: javascript typescript generics subclass


【解决方案1】:
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;
  }
}

【讨论】:

  • 不,我有 100 个组件,我不能只将所有内容都粘贴在基类中。然后智能感知将只列出所有内容,这几乎与不列出任何内容一样糟糕。
【解决方案2】:

我设法使用 TypeScript 的 conditional types 一起破解了一些东西。这并不理想,但它可能对你有用:

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

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 基类和@ 987654333@ 不再需要调用它的构造函数,因此您可以在那里赢得一些代码。

这是TS playground link

【讨论】:

  • 这是否适用于两个以上的组件?我有大约 100 个
  • @RyanPeschel 如果您将它们全部输入TypeMapper,它应该。你会去T extends CT.One ? One : T extends CT.Two ? Two : ... T extends CT.NinetyNine ? NinetyNine : Hundred
  • 呃,这真的是最好的方法吗?
猜你喜欢
  • 2021-05-28
  • 1970-01-01
  • 2016-05-20
  • 2020-08-31
  • 1970-01-01
  • 1970-01-01
  • 2021-05-01
  • 1970-01-01
  • 2020-04-27
相关资源
最近更新 更多