繁体   English   中英

如果 function 将超类作为其返回类型,TypeScript 是否可以返回子类?

[英]Can TypeScript return the subclass if a function has the superclass as its return type?

问题

错误: Property 'attach' does not exist on type 'Component'. 如果字典类型具有超类的返回值,如何返回存储在自定义字典类型中的任意子类,并使用仅存在于子类上的方法?

语境

我有一个包含许多子类的Component class。 我的GameActor class 可以附加组件。 附加组件存储在自定义类型的ComponentContainer成员中,并且可以是Component的任何子类。 例如MovementComponentHealthComponentEventComponent等都可以存储在ComponentContainer中。

当我检索附加的组件时,当我尝试从检索到的组件调用方法时,我收到上面的“属性不存在”错误。 如果我打开浏览器的开发工具,我可以看到我的返回类型的日志看起来像是,实际上是子类类型

ComponentContainer 类型定义

//globals.d.ts
// ComponentContainer type definition
// populated data structure will look like:
// {
//   "EventComponent": EventComponent,
//   "MovementComponent": MovementComponent,
//   "FooComponent": FooComponent
//   // etc...
// }
type ComponentContainer = {
  [componentName: string]: Component;
}

GameActor 有许多方法可以添加、删除、获取和列出附加组件。


//GameActor class that can have Components attached to it
export abstract class GameActor extends GameObject implements IGameActor {

  protected components: ComponentContainer;

  constructor() {
    this.components = {};
  }

  public getComponent(key: string): Component|null {
      return this.components[key];
  }
}

// Player subclass of GameActor
export class Player extends GameActor implements IPlayer {
  //Player class code here...
}

//Component superclass
export abstract class Component {
  protected typeId: string;

  public getTypeId(): string {
    return this.typeId;
  }
}

//EventComponent subclass
export class EventComponent extends Component implements IEventComponent {

  public attach(event: Event|CustomEvent): void {
    //Attach code here...
  }
}

现在在代码的其他地方我想做以下事情:

this.getComponent("EventComponent").attach(PlayerDeathEvent.create(this));

此时我收到错误。 如果我注销以下代码,看起来两者都是EventComponent类型。

let ec = this.Player.getComponent("EventComponent");
let t = new EventComponent(); 

浏览器控制台日志

我希望.attach不会抛出错误,因为编译器知道该组件的类型是EventComponent

这里的问题是编译器没有从Component | null缩小this.getComponent("EventComponent")的结果所需的信息。 Component | nullEventComponent getComponent()方法被声明为接受一个string并返回一个Component | null Component | null ,这就是编译器真正知道的一切。

确实,编译器会进行一定量的控制流分析,其中它会将值的类型细化为更具体的类型,即它们的声明方式,基于它们的使用方式......但这只会发生在非常特定的情况下并且是绝不是完美的 编译器无法查看任意代码并在运行之前弄清楚运行时究竟会发生什么。 好吧,从技术上讲,没有人能做到这一点 但是,是的,在很多情况下,编译器不知道对人类来说显而易见的东西。 这是一个简化的示例:

function hmm(x: number) {
    return (x >= 0) ? x : "negative";
}

console.log(hmm(4).toFixed()); // error!
// --------------> ~~~~~~~
// "toFixed" does not exist on number | "negative"

编译器只知道hmm()接受一个number并返回一个number | "negative" number | "negative" 对人类来说很明显hmm(4)将返回一个数字,因此将具有一个toFixed()方法,并且确实在运行时代码运行没有错误。 但是编译器不知道hmm(4)最终会评估4 >= 0 ,也不知道4 >= 0最终会返回true ,所以它不知道hmm(4)不会返回"negative" ,这意味着它不知道hmm(4)会有一个toFixed()方法,这意味着......编译器错误。


如果您通过更强的类型显式地向编译器提供信息,您将获得更好的结果,而不是期望编译器从代码流中弄清楚会发生什么。 我的建议是加强您的ComponentContainer类型以表示您在评论中暗示的键值映射,并使getComponent()成为一个通用方法,其中key参数是来自keyof ComponentContainer的通用类型K ,它返回一个类型的值ComponentContainer[K] (当您查找ComponentContainer对象的K属性时,您会得到)。 像这样:

type ComponentContainer = {
    EventComponent: EventComponent;
    // MovementComponent: MovementComponent,
    // FooComponent: FooComponent
    // etc
}

abstract class GameActor {

    protected components: ComponentContainer;

    constructor() {
        // better actually initialize this properly
        this.components = {
            EventComponent: new EventComponent()
            // MovementComponent: new MovementComponent();
            // FooComponent: new FooComponent();
            // etc
        }
    }

    public getComponent<K extends keyof ComponentContainer>(key: K): ComponentContainer[K] {
        return this.components[key];
    }
}

现在,当您调用您的方法时,它应该按您期望的方式工作:

// Player subclass of GameActor
class Player extends GameActor {
    bloop() {
        this.getComponent("EventComponent").attach(PlayerDeathEvent.create(this)); // okay

    }
}

好的,希望有帮助; 祝你好运!

链接到代码

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM