Может ли TypeScript вернуть подкласс, если функция имеет суперкласс в качестве возвращаемого типа? - PullRequest
1 голос
/ 26 октября 2019

Проблема

Ошибка: Property 'attach' does not exist on type 'Component'. Как вернуть произвольный подкласс, сохраненный в пользовательском типе словаря, и использовать метод, который существует только в подклассе, если тип словаря имеет возвращаемое значение суперкласса?

Context

У меня есть класс Component, который имеет много подклассов. Мой класс GameActor может иметь прикрепленные к нему компоненты. Присоединенный компонент хранится в элементе ComponentContainer, который является пользовательским типом и может быть любым из подклассов Component. Например, MovementComponent, HealthComponent, EventComponent и т. Д. Могут быть сохранены в 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(); 

browser console log

Я ожидаю, что .attach не выдаст ошибку, потому что компилятор знаеткомпонент имеет тип EventComponent.

1 Ответ

1 голос
/ 27 октября 2019

Проблема здесь в том, что у компилятора нет информации, необходимой для сужения результатов this.getComponent("EventComponent") с Component | null до EventComponent. Объявлен метод getComponent(), который принимает string и возвращает 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". Для человека очевидно, что hmm(4) вернет число и, следовательно, будет иметь метод toFixed(), и, конечно же, во время выполнения код выполняется без ошибок. Но компилятор не знает, что hmm(4) будет в итоге вычислять 4 >= 0, и он не знает, что 4 >= 0 в конечном итоге вернет true, и поэтому он не знает, что hmm(4) не будетвернуть "negative", что означает, что он не знает, что hmm(4) будет иметь метод toFixed(), что означает ... ошибка компилятора.


Вместо того, чтобы ожидать, что компилятор выяснит, что произойдет из потока кода, у вас будут лучшие результаты, если вы дадите компилятору информацию явно через более сильные типы. Я рекомендую усилить ваш тип ComponentContainer, чтобы он отображал отображение значения ключа, которое вы подразумевали в ваших комментариях, и сделать getComponent() универсальным методом, в котором параметр key имеет универсальный тип K из числа keyof ComponentContainer,и он возвращает значение типа ComponentContainer[K] (это то, что вы получаете, когда просматриваете свойство K объекта ComponentContainer). Например:

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

    }
}

Хорошо, надеюсь, это поможет;удачи!

Ссылка на код

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...