I'm starting to delve into generics and have a generic event class that looks like this
export interface Listener < T > {
(event: T): any;
}
export class EventTyped < T > {
//Array of listeners
private listeners: Listener < T > [] = [];
Attach(listener: Listener < T > ) {
this.listeners.push(listener);
}
Emit(event: T) {
this.listeners.forEach(listener => listener(event));
}
}
I create my event like this onPageSizeSelected = new EventType<PageSizeSelector>();
My listeners signature is this PageSizeSelectedHandler(event:Event,object:PageSizeSelector)
.
When I go to attach the event like this pageSizeSelector.onPageSizeSelected.Attach(this.PageSizeSelectedHandler.bind(this))
no error is thrown.
When the handler is attached like this pageSizeSelector.onPageSizeSelected.Attach(this.PageSizeSelectedHandler)
. It immediately picks up the the method signature is incorrect and has too many parameters.
What is bind doing that typescript can't correctly infer the method signature? How can I safely keep my this
and have my event strongly typed?
If all you want is for the compiler to catch bound methods having the wrong number of parameters and you don't care about the this
context, you can just make sure to enable the --strictBindCallApply
compiler option:
class StringListeningClassThing {
myString = "hey";
oneParam(x: string) {
return x + this.myString;
}
twoParams(x: number, y: string) {
return x.toFixed(2) + y + this.myString;
}
}
const onPageSizeSelected = new EventTyped<string>();
const stringListenerThingy = new StringListeningClassThing();
onPageSizeSelected.Attach(
stringListenerThingy.twoParams); // error
onPageSizeSelected.Attach(
stringListenerThingy.twoParams.bind(stringListenerThingy)); // error
onPageSizeSelected.Attach(
stringListenerThingy.oneParam.bind(stringListenerThingy)); // okay
onPageSizeSelected.Attach(
stringListenerThingy.twoParams.bind(stringListenerThingy, 2)); // okay
This might be all you need. But there are still some type safety issues here:
Unfortunately TypeScript doesn't do a great job of type-checking this
contexts automatically:
onPageSizeSelected.Attach(
stringListenerThingy.oneParam); // no error
onPageSizeSelected.Attach(
stringListenerThingy.oneParam.bind({ notGood: true })); // no error
The fact that those are accepted means you will have runtime errors, as stringListenerThingy
's methods dereference a bad this
.
There is a suggestion at microsoft/TypeScript#7968 to add something like a --strictThis
compiler option which would prevent you from passing around mis-bound functions, but it hasn't yet been implemented, apparently because it would both break lots of existing code and have a significant compiler performance impact. If you want to see that implemented you might want to go to that issue and give it a 👍 and/or describe your use case (if it is particularly compelling and not already mentioned in that issue).
If you really want to make the compiler do this checking, it is possible, but you will need to add this
parameters manually to all sorts of places in your code. For example, you could do something like this:
// explicitly add void this-context to definition of Listener
export interface Listener<T> {
(this: void, event: T): any;
}
// explicitly add class-based this-context to all methods
class StringListeningClassThing {
myString = "hey";
oneParam(this: StringListeningClassThing, x: string) {
return x + this.myString;
}
twoParams(this: StringListeningClassThing, x: number, y: string) {
return x.toFixed(2) + y + this.myString;
}
}
And now the above examples give the desired errors:
// enjoy type safety
onPageSizeSelected.Attach(
stringListenerThingy.oneParam); // error
onPageSizeSelected.Attach(
stringListenerThingy.oneParam.bind({ notGood: true })); // error
So the compiler can enforce this stuff, but not automatically, until and unless --strictThis
becomes a thing.
Playground link to code
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With