typescript - Tipo implícito de `this` para métodos de clase
EDITAR: Parece que este es un problema conocido en Typescript. Se implementó una solución en su momento, pero finalmente se retiró debido a problemas de rendimiento irresolubles.
Esta situación surge a menudo en nuestra base de código:
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);
}
}
La solución en Typescript es esta:
consumer( () => this.getValue() ); // capture correct this
O esto:
consumer( this.getValue.bind(this) ); // bind to correct this
Este problema podría ser obvio para un programador de Typescript/Javascript, pero nuestro equipo está portando un gran cuerpo de C# a Typescript, y en C# esto no es un error (es decir, un método pasado en C# se vincula automáticamente a la instancia del objeto). Así que me gustaría que el sistema de tipos detectara este error, si es posible.
El primer paso obvio para escribir explícitamente el this utilizado en la devolución de llamada:
function consumer<T>(valueProducer: (this: void) => T) {
let value = valueProducer();
console.log(value);
}
Esperaría que eso fuera suficiente, pero resulta que también necesito escribir explícitamente el parámetro this en el método Foo:
class Foo {
getValue(this: Foo): number { // I could also have written getValue(this: this)
return this._value;
}
Con estas dos cosas en su lugar, ahora obtengo exactamente el error que quiero:
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'.
Sin embargo, no quiero tener que agregar this: this a cada método en mi aplicación. ¿No debería el valor de this en un método ser implícitamente el del tipo que lo contiene? ¿Hay otra manera de lograr este mismo resultado sin agregar todo ese ruido de código repetitivo a mis clases?
(plunker para el código discutido)
Respuesta #1
Podrías usar EsLint con un complemento para TypeScript. Tiene una regla que prohíbe los métodos sin enlace:
211:19 error Avoid referencing unbound methods which may cause unintentional scoping of `this` @typescript-eslint/unbound-method
Usar EsLint generalmente es una buena idea, por lo que esto no debería ser un problema. Ten en cuenta que esta regla requiere verificación de tipos, por lo que necesitas extender de todos los conjuntos de reglas en el plugin:
{
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended"
]
}
Por supuesto, podrías habilitar solo esta regla, pero seguir las configuraciones recomendadas es una mejor idea.
Respuesta #2
Podrías pasar el argumento this explícitamente al callback, de la misma manera en que las funciones incorporadas de JavaScript como map también soportan un this explícito:
function consumer<T>(valueProducer: () => T, thisArg: any) {
let value = valueProducer.apply(thisArg);
console.log(value);
}
class Foo {
private _value: number = 100;
getValue(): number {
return this._value;
}
constructor() {
consumer(this.getValue, this);
}
}
Desde mi punto de vista, más feo que el enfoque de la función flecha, pero al menos no puedes olvidar pasarlo.
