发生错误的原因是Events.register()
接受一个回调,该回调应该接受任何IEvent
.因此,用尽可能少的IEvent
调用回调应该是完全可以接受的(在您的例子中,因为IEvent
是一个空接口,所以这只是{}
,空对象):
public static register(callback: (event: IEvent) => void, eventClassName: string): void {
callback({}); // <-- look, no error
}
另一方面,onSomethingCreatedEvent()
方法希望它的输入是SomethingCreatedEvent
,所以这个方法访问SomethingCreatedEvent
个对象特有的事件属性应该是完全可以接受的,比如something
属性(我假设它的值是string
,因为你没有在代码中定义Something
类型.也就是说,我的行为就像type Something = string;
):
private async onSomethingCreatedEvent(event: SomethingCreatedEvent): Promise<void> {
console.log(event.something.toUpperCase());
}
但现在,在setupSubscriptions()
中,您正在向Events.register()
传递一个只接受SomethingCreatedEvent
个事件的回调,这是一个错误:
setupSubscriptions(): void {
Events.register(this.onSomethingCreatedEvent.bind(this), SomethingCreatedEvent.name); // error!
// -----------> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Argument of type '(event: SomethingCreatedEvent) => Promise<void>' is not
// assignable to parameter of type '(event: IEvent) => void'.
}
这是一个有充分理由的错误.如果按上面修改的方式调用代码,则会出现运行时错误,因为我们调用的回调输入错误:
new AfterSomethingCreated();
// RUNTIME ERROR: Uncaught (in promise) TypeError: event.something is undefined
因为这是现有的代码,所以这在实践中可能不会发生.实际上,现有的一些JavaScript在技术上是不安全的.TypeScript以bivariant的方式判断method parameters,这意味着它将允许安全的缩小和不安全的扩大操作.Function个参数的判断更加严格(假设启用了the --strictFunctionTypes
compiler option个参数,这是the --strict
suite of compiler features的一部分).
如果想要获得更松散的类型行为,需要将callback
的类型表示为method,而不是function.顺便说一下,区别在于:
interface Test {
functionSyntax: (ev: IEvent) => void;
methodSyntax(ev: IEvent): void;
}
const test: Test = {
functionSyntax: (ev: SomethingCreatedEvent) => { }, // error!
methodSyntax: (ev: SomethingCreatedEvent) => { } // okay!
}
看看Test
中functionSyntax
的声明如何是一个带有箭头函数表达式类型的属性,而methodSyntax
看起来更像是一个方法声明.并看看如何实施test
个投诉关于functionSyntax
个属性接受太窄的一种类型,而methodSyntax
个属性不投诉.
因此,如果您只想 suppress 错误,可以依赖方法语法.这有点棘手,因为独立函数没有方法语法.你不能把(ev: IEvent): void
写成一个类型,而{(ev: IEvent): void}
被当作函数语法.这里的技巧是创建一个实际的方法类型,然后在周围的对象中创建index:
type MethodSyntax = { method(event: IEvent): void }["method"]
// type MethodSyntax = (event: IEvent) => void, but marked as a method
现在如果你用这个写Events.register()
:
public static register(callback: MethodSyntax, eventClassName: string): void { }
然后你的电话会突然正常工作:
Events.register(this.onSomethingCreatedEvent.bind(this), SomethingCreatedEvent.name); // okay
这不再是类型安全的,但至少它没有错误.
如果您关心强制执行类型安全,那么您可能需要重构,以便在处理程序处理回调时不会发生任何不好的事情.以下是一种可能的方法:
class Events {
static handlers: ((event: IEvent) => void)[] = [];
public static register<T extends IEvent>(
callback: (event: T) => void,
eventClass: new (...args: any) => T
): void {
this.handlers.push(ev => ev instanceof eventClass && callback(ev));
}
public static handleEvent(event: IEvent) {
this.handlers.forEach(h => h(event));
}
}
现在Events.register()
是一个generic函数,它接受一个只接受T
类型事件的callback
,以及一个eventClass
构造函数(而不是类name).这样就可以 for each 事件调用每个处理程序...除非event instanceof eventClass
,否则我们不会打callback(event)
.只有类name,编译器很难验证任何特定事件是否适合任何特定回调,因为类的name
属性在TypeScript中不是强类型的(有关更多信息,请参阅microsoft/TypeScript#43325和内部链接的问题).
然后现在SomethingCreatedEvent
美元接受以下条件:
setupSubscriptions(): void {
Events.register(this.onSomethingCreatedEvent.bind(this), SomethingCreatedEvent);
}
虽然有些不合适的东西会被标记:
Events.register((o: SomethingCreatedEvent) => { }, Date) // error!
// Property 'something' is missing in type 'Date' but required in type 'SomethingCreatedEvent'.
Playground link to code