Расширение переопределений - PullRequest
0 голосов
/ 30 августа 2018

В TypeScript у меня есть класс, расширяющий модуль Events, который имеет следующее в своем объявлении:

on(event: string | symbol, listener: (...args: any[]) => void): this;

Класс, расширяющий модуль, генерирует ряд событий с разными сигнатурами для слушателя. Я могу создать несколько переопределений для этого свойства, которые немного более специфичны, но все же соответствуют подписи. Что-то вроде:

export class Agent extends Events {
  constructor(conf: IAgentConf);
  on(event: 'eventA', listener: (body: IAEvent) => void): this
  on(event: 'eventB', listener: (body: IPayload<IBEvent>) => void):this;
  on(event: 'eventC', listener: (body: ICEvent[]) => void): this;
  ...
}

Используя эту типизацию, TypeScript может определить форму обратного вызова при объявлении прослушивателей событий.

Однако у меня возникают проблемы, когда я продолжаю расширять этот объект новым объектом, который генерирует новое событие:

class MyAgent extends Agent {
  static EventD: string = 'EventD';
  init: () => void;
  on(event: 'EventD', listener: (body: IEventD) => void):this;
  constructor(conf: IAgentConf) {
    super(conf);

    this.init = () => {
      this.on('EventA', body => {
        this.emit(MyAgent.EventD, body.thingy);
      });
    };

    init();
  }
}

Это, к сожалению, не работает. Я получаю ошибку:

(TS) Property 'on' in type 'MyAgent' is not assignable to the same property in base type 'Agent'.

Возможно ли далее переопределить свойство из класса прародителя в классе внука?

1 Ответ

0 голосов
/ 30 августа 2018

Как правило, подкласс может переопределять свойство (или метод) суперкласса, если свойство подкласса присваивается свойству родительского класса. Для метода с несколькими сигнатурами вызовов, я полагаю, что правило состоит в том, что каждая сигнатура вызова в суперклассе должна иметь соответствующую сигнатуру вызова в подклассе, которая может быть назначена ему. Это «присваиваемое» отношение немного мутное, потому что параметры сравниваются бивариантно .

Чтобы методы были совместимы в вашем случае, каждый класс должен повторно объявить все типы событий из суперклассов, а также общую сигнатуру вызова:

class Agent extends Events {
  on(event: 'eventA', listener: (body: IAEvent) => void): this
  on(event: 'eventB', listener: (body: IPayload<IBEvent>) => void):this;
  on(event: 'eventC', listener: (body: ICEvent[]) => void): this;
  on(event: string | symbol, listener: (...args: any[]) => void): this;
  on(event: string | symbol, listener: (...args: any[]) => void): this { 
    return super.on(event, listener);
  }
}
class MyAgent extends Agent {
  on(event: 'eventA', listener: (body: IAEvent) => void): this
  on(event: 'eventB', listener: (body: IPayload<IBEvent>) => void):this;
  on(event: 'eventC', listener: (body: ICEvent[]) => void): this;
  on(event: 'EventD', listener: (body: IEventD) => void):this;
  on(event: string | symbol, listener: (...args: any[]) => void): this;
  on(event: string | symbol, listener: (...args: any[]) => void): this { 
    return super.on(event, listener);
  }
}

Вы можете немного сократить дублирование, определив интерфейс только для метода on и расширив его:

interface EventsOn<This> { 
  (event: string | symbol, listener: (...args: any[]) => void): This;
}
class Events {
  on(event: string | symbol, listener: (...args: any[]) => void): this {
    // ...
    return this;
  }
}
interface AgentOn<This> extends EventsOn<This> { 
  (event: 'eventA', listener: (body: IAEvent) => void): This;
  (event: 'eventB', listener: (body: IPayload<IBEvent>) => void):This;
  (event: 'eventC', listener: (body: ICEvent[]) => void): This;
}
interface Agent {
  on: AgentOn<this>;
}
class Agent extends Events { }
interface MyAgentOn<This> extends AgentOn<This> { 
  (event: 'EventD', listener: (body: IEventD) => void):This;
}
interface MyAgent { 
  on: MyAgentOn<this>;
}
class MyAgent extends Agent { }

(Когда вы объявляете интерфейс для типа функции с сигнатурами вызовов, расширение интерфейса, по-видимому, накапливает сигнатуры вызовов, тогда как при объявлении интерфейса с помощью метода on расширение интерфейса, похоже, теряет сигнатуры вызовов из суперинтерфейс.)

...