Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Typescript Types and .bind

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?

like image 293
Stevenfowler16 Avatar asked Feb 15 '19 14:02

Stevenfowler16


1 Answers

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

like image 199
jcalz Avatar answered Oct 18 '22 14:10

jcalz