EDIT: It appears this is a known issue in Typescript. A solution was once implemented but ultimately pulled because of unresolvable performance problems.
This situation comes up often in our codebase:
function consumer<T>(valueProducer: () => T) {
let value = valueProducer();
console.log(value);
}
class Foo {
private _value: number = 100;
getValue(): number {
return this._value;
}
constructor() {
// Oops! Inside consumer(), getValue will be called with wrong this
consumer(this.getValue);
}
}
The solution in Typescript is either this:
consumer( () => this.getValue() ); // capture correct this
Or this:
consumer( this.getValue.bind(this) ); // bind to correct this
This problem might be obvious to a Typescript/Javascript programmer, but our team is porting a large body of C# to Typescript, and in C# this is not an error (i.e. a passed method in C# is automatically bound to the object instance). So I'd like the type system to catch this error, if possible.
The first, obvious step to explicitly type the this
used on the callback:
function consumer<T>(valueProducer: (this: void) => T) {
let value = valueProducer();
console.log(value);
}
I would expect that to be enough, but it turns out I also need to explicitly type the this
parameter on the Foo method:
class Foo {
getValue(this: Foo): number { // I could also have written getValue(this: this)
return this._value;
}
With these two things in place, I now get exactly the error I want:
error TS2345: Argument of type '(this: Foo) => number' is not assignable to parameter of type '(this: void) => number'.
The 'this' types of each signature are incompatible.
Type 'void' is not assignable to type 'Foo'.
However, I don't want to have to add this: this
to every method in my app. Shouldn't the value of this
in a method implicitly be that of the enclosing type? Is there another way to achieve this same result without adding all that boilerplate noise to my classes?
(plunker for discussed code)
An implicit class is a class marked with the implicit keyword. This keyword makes the class's primary constructor available for implicit conversions when the class is in scope. Implicit classes were proposed in SIP-13.
The implicit parameter in Java is the object that the method belongs to. It's passed by specifying the reference or variable of the object before the name of the method. An implicit parameter is opposite to an explicit parameter, which is passed when specifying the parameter in the parenthesis of a method call.
In Scala, objects and values are treated mostly the same. An implicit object can be thought of as a value which is found in the process of looking up an implicit of its type.
A type class is an abstract, parameterized type that lets you add new behavior to any closed data type without using sub-typing. If you are coming from Java, you can think of type classes as something like java.
You could use EsLint with a plugin for TypeScript. It has a rule that disallows unbound methods:
211:19 error Avoid referencing unbound methods which may cause unintentional scoping of `this` @typescript-eslint/unbound-method
Using EsLint is generally a good idea, so this shouldn't be a problem. Note that this rule requires type checking, so you need to extend from all the rulesets in the plugin:
{
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended"
]
}
You could, of course, enable this rule only, but sticking with recommended configs is a better idea.
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