Выведение типов аргументов дженерик метода, в теле дочернего класса
Сложно сформулировать четкий вопрос. Проще показать на примере: Ссылка на TS Playground
Хочу сделать типизированный EventEmitter, с возможностью наследования и расширения списка типизированных событий. Но typescript вставляет "палки в колеса".
Если среди вас есть гуру typescript, пожалуйста, помогите!
// базовый тип списка событий
type TEventsList = Record<string, (...args: [any]) => void>;
// есть типизированный класс EventEmitter
declare class TypedEventEmitter<TEvents extends TEventsList> {
addListener<E extends keyof TEvents>(event: E, listener: TEvents[E]): this;
on<E extends keyof TEvents>(event: E, listener: TEvents[E]): this;
once<E extends keyof TEvents>(event: E, listener: TEvents[E]): this;
removeListener<E extends keyof TEvents>(event: E, listener: TEvents[E]): this;
off<E extends keyof TEvents>(event: E, listener: TEvents[E]): this;
removeAllListeners<E extends keyof TEvents>(event: E): this;
setMaxListeners(n: number): this;
getMaxListeners(): number;
protectedlisteners<E extends keyof TEvents>(event: E): TEvents[E][];
rawListeners<E extends keyof TEvents>(event: E): TEvents[E][];
listenerCount<E extends keyof TEvents>(type: E): number;
prependListener<E extends keyof TEvents>(event: E, listener: TEvents[E]): this;
prependOnceListener<E extends keyof TEvents>(event: E, listener: TEvents[E]): this;
eventNames<E extends keyof TEvents>(): Array<E>;
// проблема с этим
emit<E extends keyof TEvents, A extends Parameters<TEvents[E]>>(event: E, ...args: A): boolean;
// единственный вариант сделать так, но тогда не будет работать проверка типов аргументов вызываемых событий
// emit<E extends keyof TEvents>(event: E, ...args: any[]): boolean;
}
// события которые есть у BaseClass
type TBaseClassEvents = {
event1(): void;
event2(arg1: string): void;
event3(arg1: number, arg2: boolean): void;
}
// есть некий класс, у которого есть свои события и от которого можно унаследоваться, добавив ему дополнительные события
class BaseClass<TAdditionalEvents extends TEventsList> extends TypedEventEmitter<TAdditionalEvents & TBaseClassEvents> {
testBaseClassMethod() {
// проблема тут, тайпскрипт ругается
this.emit("event2", "Hello from BaseClass"); // тут ошибка
}
}
const baseClassInstance = new BaseClass();
baseClassInstance.emit("event2", "Hello from BaseClass"); // тут уже нет ошибки
// некий дочерний класс унаследованный от BaseClass
class ChildClass extends BaseClass<{ event4(arg1: boolean): void }> {
testChildClassMethod() {
// тут все прекрасно работает
this.emit("event1");
this.emit("event2", "Hello from ChildClass");
this.emit("event4", true);
}
}
const childClassInstance = new ChildClass();
childClassInstance.emit("event2", "Hello from BaseClass"); // и тут нет ошибки
Ответы (1 шт):
Автор решения: qwabra
→ Ссылка
1
// некий дочерний класс унаследованный от
BaseClass
class ChildClass extends BaseClass<{ event4(arg1: boolean): void }>
"BaseClass" это TypedEventEmitter !
// -,-
// -,-
type TEventsList = Record<string, (...args: any[]) => void>
// -,-
declare class TypedEventEmitter<TEvents extends TEventsList> {
addListener<E extends keyof TEvents>(event: E, listener: TEvents[E]): this
on<E extends keyof TEvents>(event: E, listener: TEvents[E]): this
once<E extends keyof TEvents>(event: E, listener: TEvents[E]): this
removeListener<E extends keyof TEvents>(event: E, listener: TEvents[E]): this
off<E extends keyof TEvents>(event: E, listener: TEvents[E]): this
removeAllListeners<E extends keyof TEvents>(event: E): this
setMaxListeners(n: number): this
getMaxListeners(): number
protectedlisteners<E extends keyof TEvents>(event: E): TEvents[E][]
rawListeners<E extends keyof TEvents>(event: E): TEvents[E][]
listenerCount<E extends keyof TEvents>(type: E): number
prependListener<E extends keyof TEvents>(event: E, listener: TEvents[E]): this
prependOnceListener<E extends keyof TEvents>(event: E, listener: TEvents[E]): this
eventNames<E extends keyof TEvents>(): Array<E>
// проблема с этим
emit<E extends keyof TEvents, A extends Parameters<TEvents[E]>>(event: E, ...args: A): boolean
}
// -, -
// -,-
// - ,-
namespace ClassA {
export interface TEvents extends TEventsList {
event1(): void
event2(arg1: string): void
event3(arg1: number, arg2: boolean): void
}
}
class ClassA extends TypedEventEmitter<ClassA.TEvents> {
testClassAMethod() {
this.emit("event2", "Hello from ClassA")
this.emit("event1")
}
}
{//test
const instance = new ClassA
// -,-
instance.emit("event2", 'str')
// @ts-expect-error
instance.emit("event2", 123)
// @ts-expect-error
instance.emit("event2")
}
// -, -
// -,-
// - ,-
namespace ClassB {
export type TEvents = ClassA.TEvents
& { event4(arg1: boolean): void }
& { event8(arg1?: boolean): void }
}
class ClassB extends TypedEventEmitter<ClassB.TEvents> {
testClassBMethod() {
this.emit("event1")
this.emit("event2", "Hello from ClassB")
this.emit("event4", true)
}
}
// -,-
{//test
const instance = new ClassB
// -,-
{
instance.emit("event4", true)
// @ts-expect-error
instance.emit("event4", 123)
// @ts-expect-error
instance.emit("event4")
}
// -,-
{
instance.emit("event8")
}
}
// -, -
UPD: fix autocomplete
type TEventsList = Record<string, (...args: any[]) => void>
declare class TypedEventEmitter<TEvents extends TEventsList> {
emit<E extends keyof TEvents, A extends Parameters<TEvents[E]>>(event: E, ...args: A): boolean
}
// -,-
namespace ClassA {
// export interface TEvents extends TEventsList {
// event1(): void
// event2(arg1: string): void
// event3(arg1: number, arg2: boolean): void
// }
// TODO: fix autocomplete
export type TEvents = {
event1(): void
event2(arg1: string): void
event3(arg1: number, arg2: boolean): void
}
}
class ClassA extends TypedEventEmitter<ClassA.TEvents> { }
{//test
const instance = new ClassA
instance.emit('event1')
}
EventTarget
EventTarget - https://developer.mozilla.org/ru/docs/Web/API/EventTarget
class Q extends EventTarget { }
{//test
const q = new Q
q.addEventListener('qwa', () => { })
const qwa = new Event('qwa')
q.dispatchEvent(qwa)
}