41

TypeScriptクラスがあり、コールバックとして使用する関数があります。

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

別の関数に渡します

this.deleteRow(this.removeRow);

次に、jQuery Ajaxメソッドを呼び出します。このメソッドが成功すると、次のようにコールバックが呼び出されます。

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

上記のように、クラスへの「this」参照を保持できる唯一の方法は、それをコールバックに渡すことです。動作しますが、ズボンのコードです。'this'をこのように接続しないと(申し訳ありません)、コールバックメソッドでこれを参照するとWindowオブジェクトに戻ります。私はずっと矢印関数を使用しているので、クラスの他の場所にあるように、「this」はクラス自体になると予想しました。

字句スコープを維持しながら、TypeScriptでコールバックを渡す方法を知っている人はいますか?

4

5 に答える 5

60

2014-01-28を編集:

新しい読者の方は、以下のザックの回答を確認してください。

彼は、太い矢印の構文を使用して、クラス定義でスコープ関数を定義およびインスタンス化できる、非常に優れたソリューションを提供しています。

私が追加する唯一のことは、Zacの回答のオプション5に関して、次の構文を使用して、メソッドのシグネチャとリターンタイプを繰り返すことなく指定できることです。

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

2013-05-28を編集:

関数プロパティタイプを定義するための構文が変更されました(TypeScriptバージョン0.8以降)。

以前は、次のような関数型を定義していました。

class Test {
   removeRow: (): void;
}

これは現在、次のように変更されています。

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

この新しい変更を含めるために、以下の回答を更新しました。

さらに余談ですが、同じ関数名に対して複数の関数シグネチャを定義する必要がある場合(ランタイム関数のオーバーロードなど)、オブジェクトマップ表記を使用できます(これはjQuery記述子ファイルで広く使用されています)。

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

の署名をクラスのプロパティとして定義する必要がありますremoveRow()が、コンストラクターで実装を割り当てます。

これを行うには、いくつかの異なる方法があります。

オプション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.
        }
    }

}

コンストラクターを最小限に抑えたい場合removeRowは、クラス定義にメソッドを保持し、コンストラクターにプロキシ関数を割り当てることができます。

オプション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.
    }

}

オプション3

最後に、アンダースコアやjQueryなどのライブラリを使用している場合は、それらのユーティリティメソッドを使用してプロキシを作成できます。

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.
    }

}

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!");
    });
}
于 2013-01-23T04:18:56.400 に答える
35

更新:Slyの更新された回答を参照してください。以下のオプションの改良版が組み込まれています。

別の更新:ジェネリック

クラス全体でジェネリック型を指定せずに、関数シグネチャでジェネリック型を指定したい場合があります。構文を理解するのに数回の試行が必要だったので、共有する価値があるかもしれないと思いました。

class MyClass {  //no type parameter necessary here

     public myGenericMethod = <T>(someArg:string): QPromise<T> => {

         //implementation here...

     }
}

オプション4

Sly_cardinalの回答に追加する構文をさらにいくつか示します。これらの例は、関数の宣言と実装を同じ場所に保持します。

class Test {

      // Define the method signature AND IMPLEMENTATION here.
      public removeRow: () => void = () => {

        // Perform your logic to remove the row.
        // Reference `this` as needed.

      }

      constructor (){

      }

}

また

オプション5

もう少しコンパクトですが、明示的な戻り型を放棄します(コンパイラは、明示的でない場合でも、とにかく戻り型を推測する必要があります)。

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 (){

      }

}

于 2014-01-27T16:31:15.023 に答える
5

.bind()を使用して、コールバック内のコンテキストを保持します。

実用的なコード例:

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

元の質問のコードは次のようになります。

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

コールバック関数内のコンテキスト(this)を、bind関数への引数として渡されたもの(この場合は元のthisオブジェクト)に設定します。

于 2017-05-19T20:52:38.627 に答える
1

これは別の回答からのクロスポストのようなものです(TypeScriptに「this」のエイリアスはありますか?)。上記の例を使用して、概念を再適用しました。クラスインスタンスとメソッドを呼び出す動的コンテキストエンティティの両方への「this」スコープを明示的にサポートしているため、上記のオプションよりも優れています。

以下の2つのバージョンがあります。コンパイラが正しく使用するのを支援するので、最初のものが好きです(明示的に入力されたパラメータがあるため、コールバックラムダ自体をコールバックほど簡単に誤用しようとはしません)。

テストしてください: 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();
于 2014-03-21T21:05:28.590 に答える
0

タイプを使用したslyとZacの回答に基づいて構築する:完全なHelloWorldの例。「typescriptjavascriptコールバック」を検索すると、これがGoogleでの上位の結果であるため、これが歓迎されることを願っています。

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);

これは次のようにトランスパイルされます。

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);

誰かがそれがほんの少し役立つと思うことを願っています。:)

于 2016-10-26T16:41:52.233 に答える