Come posso preservare lo scope lessicale in TypeScript con una funzione di callback

Ho una class TypeScript, con una funzione che intendo utilizzare come callback:

removeRow(_this:MyClass): void { ... // 'this' is now the window object // I must use '_this' to get the class itself ... } 

Lo passo in un’altra funzione

 this.deleteRow(this.removeRow); 

che a sua volta chiama un metodo jQuery Ajax, che, se ha successo, richiama il callback in questo modo:

 deleteItem(removeRowCallback: (_this:MyClass) => void ): void { $.ajax(action, { data: { "id": id }, type: "POST" }) .done(() => { removeRowCallback(this); }) .fail(() => { alert("There was an error!"); }); } 

L’unico modo in cui posso mantenere il “questo” riferimento alla mia class è di passarlo al callback, come dimostrato sopra. Funziona, ma è il codice pantaloni. Se non leggo il “this” come questo (mi dispiace), qualsiasi riferimento a questo nel metodo di callback è tornato all’object Window. Poiché sto utilizzando le funzioni di freccia fino in fondo, mi aspettavo che il “questo” fosse la class stessa, come è altrove nella mia class.

Qualcuno sa come passare i callback in TypeScript, preservando l’ambito lessicale?

Modifica 2014-01-28:

Nuovi lettori, assicurati di dare un’occhiata alla risposta di Zac qui sotto .

Ha una soluzione molto più ordinata che ti permetterà di definire e istanziare una funzione con scope nella definizione della class usando la syntax della fat fat.

L’unica cosa che aggiungerò è che, per quanto riguarda l’ opzione 5 nella risposta di Zac, è ansible specificare la firma del metodo e il tipo di ritorno senza alcuna ripetizione usando questa syntax:

 public myMethod = (prop1: number): string => { return 'asdf'; } 

Modifica 2013-05-28:

La syntax per la definizione di un tipo di proprietà di una funzione è stata modificata (da TypeScript versione 0.8).

In precedenza definisci un tipo di funzione come questo:

 class Test { removeRow: (): void; } 

Questo è ora cambiato in:

 class Test { removeRow: () => void; } 

Ho aggiornato la mia risposta qui sotto per includere questo nuovo cambiamento.

Inoltre: se è necessario definire più firme di funzioni per lo stesso nome di funzione (ad es. Sovraccarico della funzione di runtime), è ansible utilizzare la notazione della mappa di oggetti (ampiamente utilizzata nel file del descrittore jQuery):

 class Test { removeRow: { (): void; (param: string): string; }; } 

È necessario definire la firma per removeRow() come una proprietà sulla class, ma assegnare l’implementazione nel costruttore.

Ci sono diversi modi per farlo.

opzione 1

 class Test { // Define the method signature here. removeRow: () => void; constructor (){ // Implement the method using the fat arrow syntax. this.removeRow = () => { // Perform your logic to remove the row. // Reference `this` as needed. } } } 

Se si desidera mantenere il costruttore minimo, è sufficiente mantenere il metodo removeRow nella definizione della class e assegnare semplicemente una funzione proxy nel costruttore:

opzione 2

 class Test { // Again, define the method signature here. removeRowProxy: () => void; constructor (){ // Assign the method implementation here. this.removeRowProxy = () => { this.removeRow.apply(this, arguments); } } removeRow(): void { // ... removeRow logic here. } } 

Opzione 3

E infine, se stai usando una libreria come underscore o jQuery, puoi semplicemente usare il loro metodo di utilità per creare il proxy:

 class Test { // Define the method signature here. removeRowProxy: () => void; constructor (){ // Use jQuery to bind removeRow to this instance. this.removeRowProxy = $.proxy(this.removeRow, this); } removeRow(): void { // ... removeRow logic here. } } 

Quindi puoi riordinare un po ‘il tuo metodo deleteItem :

 // Specify `Function` as the callback type. // NOTE: You can define a specific signature if needed. deleteItem(removeRowCallback: Function ): void { $.ajax(action, { data: { "id": id }, type: "POST" }) // Pass the callback here. // // You don't need the fat arrow syntax here // because the callback has already been bound // to the correct scope. .done(removeRowCallback) .fail(() => { alert("There was an error!"); }); } 

AGGIORNAMENTO: vedi la risposta aggiornata di Sly. Incorpora una versione migliorata delle opzioni di seguito.

UN ALTRO AGGIORNAMENTO: generici

A volte si desidera specificare un tipo generico in una firma di funzione senza doverlo specificare sull’intera class. Mi ci sono voluti alcuni tentativi per capire la syntax, quindi ho pensato che valesse la pena condividere:

 class MyClass { //no type parameter necessary here public myGenericMethod = (someArg:string): QPromise => { //implementation here... } } 

Opzione 4

Ecco un paio di altre syntax da aggiungere alla risposta di Sly_cardinal. Questi esempi mantengono la dichiarazione di funzione e l’implementazione nello stesso posto:

 class Test { // Define the method signature AND IMPLEMENTATION here. public removeRow: () => void = () => { // Perform your logic to remove the row. // Reference `this` as needed. } constructor (){ } 

}

o

Opzione 5

Un po ‘più compatto, ma rinuncia al tipo di ritorno esplicito (il compilatore dovrebbe inferire comunque il tipo restituito se non esplicito):

 class Test { // Define implementation with implicit signature and correct lexical scope. public removeRow = () => { // Perform your logic to remove the row. // Reference `this` as needed. } constructor (){ } 

}

Questa è una sorta di cross post di un’altra risposta ( esiste un alias per “this” in TypeScript? ). Ho applicato nuovamente il concetto utilizzando gli esempi di cui sopra. Mi piace meglio delle opzioni precedenti perché supporta esplicitamente “questo” ambito sia per l’istanza di class sia per l’entity framework di contesto dynamic che chiama il metodo.

Ci sono due versioni di seguito. Mi piace il primo perché il compilatore aiuta a usarlo correttamente (non si cercherà facilmente di usare in modo errato il callback stesso come callback, a causa del parametro tipizzato esplicitamente).

Provalo: http://www.typescriptlang.org/Playground/

 class Test { private testString: string = "Fancy this!"; // Define the method signature here. removeRowLambdaCallback(outerThis: Test): {(): void} { alert("Defining callback for consumption"); return function(){ alert(outerThis.testString); // lexically scoped class instance alert(this); // dynamically scoped context caller // Put logic here for removing rows. Can refer to class // instance as well as "this" passed by a library such as JQuery or D3. } } // This approach looks nicer, but is more dangerous // because someone might use this method itself, rather // than the return value, as a callback. anotherRemoveRowLambdaCallback(): {(): void} { var outerThis = this; alert("Defining another callback for consumption"); return function(){ alert(outerThis.testString); // lexically scoped class instance alert(this); // dynamically scoped context caller // Put logic here for removing rows. Can refer to class // instance as well as "this" passed by a library such as JQuery or D3. } } } var t = new Test(); var callback1 = t.removeRowLambdaCallback(t); var callback2 = t.anotherRemoveRowLambdaCallback(); callback1(); callback2(); 

Utilizzare .bind () per mantenere il contesto all’interno del callback.

Esempio di codice di lavoro:

 window.addEventListener( "resize", (()=>{this.retrieveDimensionsFromElement();}).bind(this) ) 

Il codice nella domanda originale diventerebbe qualcosa del genere:

 $.ajax(action, { data: { "id": id }, type: "POST" }) .done( (() => { removeRowCallback(); }).bind(this) ) 

Imposterà il contesto (this) all’interno della funzione di callback su qualunque cosa sia stata passata come argomento per associare la funzione, in questo caso l’originale questo object.

Basandosi su sornione e le risposte di Zac con i tipi: un esempio di ciao mondo completo. Spero che questo sia il benvenuto, visto che questo è il miglior risultato in Google, durante la ricerca di “callback di javascript dattiloscritti”

 type MyCallback = () => string; class HelloWorld { // The callback public callback: MyCallback = () => { return 'world'; } // The caller public caller(callback: MyCallback) { alert('Hello ' + callback()); } } let hello = new HelloWorld(); hello.caller(hello.callback); 

Questo viene trasformato in:

 var HelloWorld = (function () { function HelloWorld() { // The callback this.callback = function () { return 'world'; }; } // The caller HelloWorld.prototype.caller = function (callback) { alert('Hello ' + callback()); }; return HelloWorld; }()); var hello = new HelloWorld(); hello.caller(hello.callback); 

Spero che qualcuno lo trovi solo un po ‘utile. 🙂