javascript - 按实现类型从类中的实现列表中获取对象的函数。(打字稿泛型)
问题描述
我有一个类实体中的组件列表。这些组件扩展了接口Component
。
class Entity {
...
const components: Component[] = [];
...
}
具体组件在哪里实现接口 Component
class SpecificComponent0 implements Component { ... }
现在我想查询实体实例e
并获取一个组件,如果它与输入查询的类型相匹配,如下所示:
const specificComponent0 = e.getSpecificComponent<SpecificComponentClass0>();
或者也许像这样
const specificComponent0 = e.getSpecificComponent(instanceof SpecificComponentClass0)
但我似乎无法在实体的get
功能中找到一种方法。
解决方案
这是一个棘手的问题,因为您正在混合运行时和构建时问题。参考您建议的示例:
const specificComponent0 = e.getSpecificComponent<SpecificComponentClass0>();
这绝对行不通,因为尖括号指定了一个“类型参数”,它只存在于 build time。由于您尝试做的事情涉及逻辑,因此您需要在运行时将某些内容传递给函数以帮助它选择正确的元素。
const specificComponent0 = e.getSpecificComponent(instanceof SpecificComponentClass0)
运算符的返回值instanceof
是一个布尔值。您正在传递true
或传递false
给这个函数,这不是很有用。
你这里有两个问题。
- 您想将某些内容传递给函数,以允许您选择正确的组件
- 我假设您希望函数返回一个缩小为正确类型的组件,而不是一般类型为
Component
问题1可以通过传入类型Constructor函数,然后与实例化的constructor属性匹配来解决Component
class Entity {
constructor(private components: Component[]) {}
getSpecificComponent(thing: new () => Component): Component | undefined {
return this.components.find(component => component.constructor === thing)
}
}
这工作得很好,但是您的getSpecificComponent
函数将返回一个类型为 的值Component | undefined
,如果您想使用仅存在于一种特定类型上的属性,这不是很有用。
为了解决问题 2(不强制转换返回值,你真的不应该这样做),我们需要
- 使函数泛型和
- 将传递给 find 的谓词转换为用户定义的类型保护,以向编译器提示如果该函数返回
true
,它可以安全地将类型缩小到泛型类型
class Component {}
class OtherThing1 extends Component { name = 'thing1' }
class OtherThing2 extends Component { name = 'thing2' }
class OtherThing3 extends Component { name = 'thing3' }
const getNarrower = <T extends Component>(thingConstructor: new () => T) =>
(thing: Component): thing is T => thing.constructor === thingConstructor
class Entity {
constructor(private components: Component[]) {}
getSpecificComponent<T extends Component>(thing: new () => T): T | undefined {
return this.components.find(getNarrower(thing))
}
}
const e = new Entity([new OtherThing1(), new OtherThing2()])
const thing = e.getSpecificComponent(OtherThing1)
console.log(thing) // [LOG]: OtherThing1: { "name": "thing1" }
const thingNotHere = e.getSpecificComponent(OtherThing3)
console.log(thingNotHere) // [LOG]: undefined