我正在写一些有趣的抽象实体系统,其中我有具有特征的实体。特质有一些字段,包括动态data
字段:
enum TraitId {
Movable = 'Movable', Rotatable = 'Rotatable', Scalable = 'Scalable', Collidable = 'Collidable'
}
interface TraitDataMovable {
x: number;
y: number;
}
type TraitDataMap = {
[TraitId.Movable]: TraitDataMovable
, [TraitId.Rotatable]: number // angle
, [TraitId.Scalable]: number // scale
, [TraitId.Collidable]: boolean // collides or not
}
interface TraitData<ID extends TraitId> {
id: ID;
data: TraitDataMap[ID];
disabled?: boolean;
}
type EntityTraits = {
[TID in TraitId]: TraitData<TID>
}
class Entity {
id: string;
traits: Partial<EntityTraits> = {};
}
到目前为止,我通过手动分配实现了正确的行为:
const ent = new Entity();
ent.traits.Rotatable = {
id: TraitId.Rotatable, // id can only be Rotatable
data: 100 // data can only be number
};
ent.traits.Collidable = {
id: TraitId.Collidable, // id can only be Collidable
data: true // data can only be boolean
}
const hasCollision = ent.traits.Collidable.data; // correctly typed as boolean
现在我正在尝试编写将任何特征添加到实体的函数:
function addTraitToEntity(entity: Entity, traitData: TraitData<TraitId>) {
entity.traits[traitData.id] = traitData;
// Type 'TraitData<TraitId>' is not assignable to type 'undefined'.
}
function addTraitToEntity2<TID extends TraitId>(entity: Entity, traitData: TraitData<TID>) {
entity.traits[traitData.id] = traitData;
// Type 'TraitData<TID>' is not assignable to type 'Partial<EntityTraits>[TID]'.
// Type 'TraitData<TID>' is not assignable to type 'undefined'
}
他们与// @ts-ignore
合作,但是我想摆脱它并正确地做。并了解如何正确键入此类系统。
答案 0 :(得分:1)
这是一种避免错误的回旋解决方案。如果您替换了整个对象entity.traits
而不是仅分配一个属性,则它将起作用,
function addTraitToEntity(entity: Entity, traitData: TraitData<TraitId>) {
entity.traits = {
...entity.traits,
[traitData.id]: traitData
}
}
我一直在解决这个问题,但是对于您的方法为什么行不通,我并没有得到令人满意的答案,但是我认为部分问题在于打字稿理解键traitData.id
匹配值traitData
。
答案 1 :(得分:1)
问题如下。 TS在尝试解析TID
表达式的类型时会以某种方式丢失有关traitData.id
的信息。因此,它目前拥有的唯一信息是对... extends TraitId
的约束。这就是TS将traitData.id
表达式解析为TraitId
或TraitId.Movable | TraitId.Scalable | TraitId.Rotatable | TraitId.Collidable
而不是TID
的原因。
实现目标的最简单方法是对现有类型使用addTraitToEntity2
函数的修改后的变体:
function addTraitToEntity2<TID extends TraitId>(entity: Entity, traitData: TraitData<TID>) {
(entity.traits[traitData.id] as any) = traitData;
}
负面因素:
as any
不是解决问题的好方法; 或按照@Linda Paiste的建议,但作为通用函数。因为没有类型变量,您将能够通过特定的id
成员和错误的data
成员来传递,反之亦然。
function addTraitToEntity2<TID extends TraitId>(entity: Entity, traitData: TraitData<TID>) {
entity.traits = {
...entity.traits,
[traitData.id]: traitData
};
}
同时,我重构了您的代码:
改进:
Entity
是作为抽象类引入的,其中包括用于添加,删除,检索特征的API; Entity
类的成员; Entity
类现在是通用的,您可以自定义实体可以具有的特征;