我准备在github上发布一个问题,但我想我先问这里。
我正在使用打字稿编写游戏的组件实体系统。 javascript中的组件实体系统(据我所知)打字稿往往不是非常类型安全的,我有一个想法试图解决这个问题。
我目前的计划是让每个依赖于许多组件的系统公开一个实体类型,该实体类型将具有该系统处理的实体应具有的许多属性。然后,我将在一个中心位置将所有各种实体类型收集到一个组合的实体联合类型中。
export type Entity =
CameraManagerEntity |
CollisionManagerEntity |
HoleManagerEntity |
ParentManagerEntity | ...
然后在一个系统中,我希望能够断言某些属性在实体上并且使用typescript推断出因为union中唯一具有该属性的类型是我的系统导出的类型,那么它必须是导出类型。
例如,假设CameraManager已导出
interface CameraManagerEntity {
foo: boolean,
bar: number
}
我的Entity union中没有其他类型有foo参数。我的期望是,如果我有一个实体联盟的实例,那么创建一个if语句来断言" foo"在实例中应该足以访问实例上的bar而没有类型错误。
function processEntity(entity: Entity) {
if ("foo" in entity) {
return entity.bar; // <-- Type error.
}
}
我错过了什么吗?我认为在一个理想的世界中,编译器应该有足够的信息来知道我的实体对象是来自它的给定的CameraManagerEntity。似乎我所建议的并不像今天的打字稿那样有用。有没有更好的方法来实现我想要做的事情?提前谢谢。
编辑:我知道用户定义的类型保护,不必手动编写类型保护,因为我认为编译器应该已经拥有所有信息。
答案 0 :(得分:3)
你想要的几乎就像是Tagged Union Type从里到外。使用标记联合类型,所有成员共享一个公共字段,TypeScript通过检查该字段的值来区分(通常使用switch
)。但在您当前的设计中,联合类型的成员通过唯一字段的存在来区分。
我想出了一个泛型类型的守护程序,它使用属性的存在来检查entity
类型:
if (hasKeyOf<CameraManagerEntity>(entity, 'camera')) {
return entity.camera;
}
以下是完整示例:
interface CameraManagerEntity {
camera: string;
}
interface CollisionManagerEntity {
collision: string;
}
type Entity = CameraManagerEntity | CollisionManagerEntity;
// Generic type guard
function hasKeyOf<T>(entity: any, key: string): entity is T {
return key in entity;
}
function processEntity(entity: Entity) {
if (hasKeyOf<CameraManagerEntity>(entity, 'camera')) {
return entity.camera;
} else if (hasKeyOf<CollisionManagerEntity>(entity, 'collision')) {
return entity.collision;
}
}
Try it in TypeScript Playground
但您可以考虑为实体系统使用标记联合类型,这可能更符合人体工程学:
interface CameraManagerEntity {
kind: 'CameraManager';
camera: string;
}
interface CollisionManagerEntity {
kind: 'CollisionManager';
collision: string;
}
type Entity = CameraManagerEntity | CollisionManagerEntity;
function processEntity(entity: Entity) {
switch (entity.kind) {
case ('CameraManager'): return entity.camera;
case ('CollisionManager'): return entity.collision;
}
}